Search code examples
haskellparsecapplicative

Applicative style parser for constructor with two arguments


I want to write a parser for a comma separated pair of values in angle brackets. I got it to work with the following approach:

pair p1 p2 = do
    x1 <- p1
    comma
    x2 <- p2
    return (x1, x2)

data Foo = Foo (Bar, Bar)

foo :: Parser Foo
foo = Foo <$> (angles $ pair bar bar)

However I would prefer the Foo constructor to take two parameter rather than a tuple:

data Foo = Foo Bar Bar

What is the best way to write such a parser? Ideally I would like to reuse standard Parsec parsers such a angles and use applicative as much as possible.


Solution

  • What is the best way to write such a parser? Ideally I would like to reuse standard Parsec parsers such a angles and use applicative as much as possible.

    In applicative style, your parser would be

    foo = angles $ Foo <$> bar <* comma <*> bar
    

    Going inside out, a bar is parsed, then a comma, which is discarded, and another bar, then the constructor Foo is applied to the two parsed bars. Finally, all is wrapped into the angles combinator, so that a string of the form

    < bar , bar >
    

    is parsed (bar should probably consume trailing whitespace).

    Combining parsers ignoring the result of one with the *> and <* applicative combinators eliminates the need for the pair combinator and easily generalises to constructors taking an arbitrary number of arguments.

    As C.A. McCann mentioned in a comment, the (<$) combinator (which is part of GHC's implementation of the Functor class, with the default implementation (<$) = fmap . const; but it's not part of the language standard) is useful too, if you want to ignore a leading token. Using that, you can write

    Foo <$ ignoreMe <*> bar <* comma <*> baz
    

    which is nicer than using parentheses

    Foo <$> (ignoreMe *> bar) <* comma <*> baz
    

    or pure,

    pure Foo <* ignoreMe <*> bar <* comma <*> baz
    

    as would be required in some form without it.