Question

I can create a parser which can handle two or three numbers, separated by comma, like this:

number :: Parser Int
number = many1 digit >>= return . read <?> "number"

numbers = do
  n1 <- number
  n2 <- char ':' >> number
  n3 <- optionMaybe $ char ':' >> number
  return ... -- return all of n1, n2, n3

Only the numbers are important, the rest can be discarded. Is there a way to concatenate the intermediate parse results (n1,n2,n3) to handle it outside of input? For example the parser-combinator of Scala can do this:

def numbers: Parser[Int ~ Int ~ Option[Int]] = // only the important numbers are returned
  number ~ (":" ~> number) ~ opt(":" ~> number)

I want to do this in order to pattern match the parser in different places. For example, in Scala I can do something like this:

val result = input.parseAs(numbers) {
  case n1 ~ n2 ~ None => // work with n1,n2
  case n1 ~ n2 ~ Some(n3) => // work with n1,n2,n3
}

where input is a string to parse. Does parsec have built-in functions to allow a similar behavior? If not how to built such a behavior by myself?

Was it helpful?

Solution

You can do it by using applicative functors. The pattern is generally:

import Control.Applicative

f <$> a1 <*> a2 <*> a3

f is some function that takes 3 arguments in this case, and a1, a2 and a3 are applicative functors over values that can be passed as arguments to f, for example if f :: Int -> Int -> Int -> Foo, a1, a2, a3 could have type Parser Int. The functors a1, a2, a3 will be applied sequentially, and their results will be collected and mapped over the function f.

In your case, you'd want to do:

numbers :: Parser (Int, Int, Maybe Int)
numbers =
  (,,)
  <$> number
  <*> (char ':' *> number)
  <*> optionMaybe (char ':' *> number)

(,,) is the constructor of a 3-tuple, so it's a function taking 3 arguments and returning a 3-tuple. Passing 3 parsers with the <$>..<*>.. pattern lifts the application of the 3-tuple constructor into the functor being used here, which is Parser in this case, so the whole expression returns the result of the mapped function wrapped in the functor, being Parser (Int, Int, Maybe Int).

You can also use liftA3 f a1 a2 a3 instead of f <$> a1 <*> a2 <*> a3; the two expressions are equivalent.

PS. You could define number using applicative functors, too (The monad interface is more "heavy-weight", and I personally try to avoid it):

number :: Parser Int
number = read <$> many1 digit <?> "number"
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top