1

So I'm creating a basic parser in Haskell, and I recently learned that instead of something like this:

sumParser = fmap (\_ a _ b _ -> a + b) ws <*> val <*> plus <*> val <*> eof

I could make it cleaner using something like

sumParser = fmap (+) ws *> val <* plus *> val <* eof

Obviously, I'm not actually doing this, but it's an example. My question is, I can 'skip' the 'return' value of certain parsers(?) like ws and val using the <* and *>. However, I'm really new to Haskell, and I'm not sure if this even makes sense or how to look it up (I don't really get it from Hoogle and looking around), but I want to be able to skip multiple of them together.

What I mean is I would like to change something like this:

ps = fmap (\_ _ a _ _ _ b _ _ -> a+b) ws <*> p1 <*> val <*> ws <*> p2 <*> ws <*> val <*> ws <*> p3

to something like

ps = fmap (\a b -> a+b) ws *> p1 *> val <* ws * p2 * ws *> val <* ws <* p3

Now that doesn't compile, and I'm not sure how to look up if this is even possible to do?

  • I'm pretty certain the first example doesn't do what you think it does. `*>` and `<*` discard the value of the left/right argument, so right off the bat you discarded the value of `fmap (+) ws` which is a function(and not the function you think it is). – Mor A. Mar 18 '18 at 22:36

1 Answers1

4

In my experience, it's getting quite confusing except in the simplest of cases to mix (*>) and (<*). I tend to use only the (<*) (and the (<$)) variants. Then I can go from left to right and only ever decide whether I want to use the result of the "next" item or not.

In your example, this would mean:

ps = (+) <$ ws <* p1 <*> val <* ws <* p2 <* pw <*> val <* ws <* p3

(Furthermore, assuming ws might be short for whitespace: you should probably not mix whitespace parsers into everything, but write abstractions that parse the whitespace more or less automatically. The common techniques are to either define parser combinators that themselves parse and discard whitespace in the end, or to first do a lexing pass and let the parser combinators operate on the resulting list/stream of tokens.)

kosmikus
  • 19,549
  • 3
  • 51
  • 66
  • Perfect, exactly what I was looking for. I don't really get the distinction between `<*` and `*>` though, are the just saying that the result of the 'next' or 'previous' ones are ignored respectively? – Michael Browning Mar 19 '18 at 05:35
  • Yes. Just look at the types: `(*>) :: Applicative f => f a -> f b -> f b`. The `a` is not mentioned in the result, so it is ignored. `(<*) :: Applicative f => f a -> f b -> f a`. The `b` is not mentioned in the result, so it is ignored. Both operators associate to the left, though, so if you mix them, strange things happen. For example `x <* y *> z` is to be read as `(x <* y) *> z` and ignores all results except the one of `z`. – kosmikus Mar 19 '18 at 06:53