22

I'm just starting with Parsec (having little experience in Haskell), and I'm a little confused about using monads or applicatives. The overall feel I had after reading "Real World Haskell", "Write You a Haskell" and a question here is that applicatives are preferred, but really I have no idea.

So my questions are:

  • What approach is preferred?
  • Can monads and applicatives be mixed (use them when they are more useful than the other)
  • If the last answer is yes, should I do it?
José Miguel
  • 270
  • 3
  • 7
  • 2
    My recommendation is to learn how to use parsec using monadic do-notation. Then as you learn about applicatives you can start using them in place of your monadic code where you can. Applicatives are strictly less powerful than monads, so there may be some things you cannot do with only applicatives. There is less of a distinction now that you use do-notation to write applicative expressions with the ApplicativeDo pragma. – ErikR Aug 01 '16 at 20:58

3 Answers3

70

It might be worth paying attention to the key semantic difference between Applicative and Monad, in order to determine when each is appropriate. Compare types:

(<*>) :: m (s -> t) -> m s -> m t
(>>=) :: m s -> (s -> m t) -> m t

To deploy <*>, you choose two computations, one of a function, the other of an argument, then their values are combined by application. To deploy >>=, you choose one computation, and you explain how you will make use of its resulting values to choose the next computation. It is the difference between "batch mode" and "interactive" operation.

When it comes to parsing, Applicative (extended with failure and choice to give Alternative) captures the context-free aspects of your grammar. You will need the extra power that Monad gives you only if you need to inspect the parse tree from part of your input in order to decide what grammar you should use for another part of your input. E.g., you might read a format descriptor, then an input in that format. Minimizing your usage of the extra power of monads tells you which value-dependencies are essential.

Shifting from parsing to parallelism, this idea of using >>= only for essential value-dependency buys you clarity about opportunities to spread load. When two computations are combined with <*>, neither need wait for the other. Applicative-when-you-can-but-monadic-when-you-must is the formula for speed. The point of ApplicativeDo is to automate the dependency analysis of code which has been written in monadic style and thus accidentally oversequentialised.

Your question also relates to coding style, about which opinions are free to differ. But let me tell you a story. I came to Haskell from Standard ML, where I was used to writing programs in direct style even if they did naughty things like throw exceptions or mutate references. What was I doing in ML? Working on an implementation of an ultra-pure type theory (which may not be named, for legal reasons). When working in that type theory, I couldn't write direct-style programs which used exceptions, but I cooked up the applicative combinators as a way of getting as close to direct style as possible.

When I moved to Haskell, I was horrified to discover the extent to which people seemed to think that programming in pseudo-imperative do-notation was just punishment for the slightest semantic impurity (apart, of course, from non-termination). I adopted the applicative combinators as a style choice (and went even closer to direct style with "idiom brackets") long before I had a grasp of the semantic distinction, i.e., that they represented a useful weakening of the monad interface. I just didn't (and still don't) like the way do-notation requires fragmentation of expression structure and the gratuitous naming of things.

That's to say, the same things that make functional code more compact and readable than imperative code also make applicative style more compact and readable than do-notation. I appreciate that ApplicativeDo is a great way to make more applicative (and in some cases that means faster) programs that were written in monadic style that you haven't the time to refactor. But otherwise, I'd argue applicative-when-you-can-but-monadic-when-you-must is also the better way to see what's going on.

pigworker
  • 42,162
  • 18
  • 118
  • 213
  • It's also worth pointing out that the parsers you can make with `` are strictly less expressive than those you can make with `>>=` -- though the extra expressivity is not always needed. This answer alludes to the differences further http://stackoverflow.com/a/7863380/261393 – Sam Elliott Aug 02 '16 at 17:20
  • Does your preference for programming in an applicative style (by which I mean what you're referring to as "a direct style", as distinct from "an `Applicative` style") extend to preferring `>>=` over `do`? – Benjamin Hodgson Aug 03 '16 at 00:32
  • 1
    @BenjaminHodgson: I thought the "direct style", at the extreme, would be one where you use function application syntax (reserved for the `->` function space in Haskell). For an example of an implementation in a modern setting, [in Idris](http://docs.idris-lang.org/en/latest/tutorial/interfaces.html?highlight=idiom#idiom-brackets) you can write `[| f x y + z |]` to mean `(+) (f x y) z` – Cactus Aug 03 '16 at 04:24
  • 1
    An example of where I've seen parallelisation of applicatives is in the Haxl ICFP'14 paper, e.g. `length intersect’ (friendsOf x) (friendsOf y)` http://community.haskell.org/~simonmar/papers/haxl-icfp14.pdf . Apart from Haxl, are there other Applicative instance examples that have parallelism built in to their implementations? – Rob Stewart Aug 03 '16 at 14:22
  • @RobStewart `Concurrently` from [`async`](https://hackage.haskell.org/package/async-2.1.0/docs/Control-Concurrent-Async.html#t:Concurrently) is parallel. – Fraser Aug 03 '16 at 18:16
  • 2
    @RobStewart Simon M's work is what I had in mind when I wrote that answer. Meanwhile, infinite streams form a monad, where `repeat` is `return` and `join` takes the diagonal of an infinite square matrix. Of course, if you want only the diagonal, it's silly to compute even the matrix of thunks: `>>=` generates at least a triangle. The sensible way to combine streams in that sense is by *zipping*, which is exactly the applicative behaviour. It may not exactly be parallelism, but it's another example where the default implementation of `` from `>>=` is inefficiently over-seqential. – pigworker Aug 03 '16 at 18:49
12

In general, start with whatever makes the most sense to you. Afterwards consider the following.

It is good practice to use Applicative (or even Functor) when possible. It is in general easier for a compiler like GHC to optimize these instances since they can be simpler than Monad. I think the general community advice post-AMP has been to make as general as possible your constraints. I would recommend the use of the GHC extension ApplicativeDo since you can uniformly use do notation while only getting an Applicative constraint when that is all that is needed.

Since the ParsecT parser type is an instance of both Applicative and Monad, you can mix and match the two. There are situations where doing this is more readable - this all depends on the situation.

Also, consider using megaparsec. megaparsec is a more actively maintained just generally cleaner more recent fork of parsec.

EDIT

Two things that, rereading my answer and the comments, I really didn't do a good job of clarifying:

  • the main benefit of using Applicative is that for many types it admits much more efficient implementations (eg. (<*>) is more performant than ap).

  • If you just want to write something like (+) <$> parseNumber <*> parseNumber, there is no need to drop into ApplicativeDo - it would be just more verbose. I'd use ApplicativeDo only when you start finding yourself writing very long or nested applicative expressions.

Alec
  • 29,819
  • 5
  • 59
  • 105
  • Thanks! I'll check megaparsec, seems good (Unicode and indentation support look great!) – José Miguel Aug 01 '16 at 21:14
  • 1
    It's not mostly about *compiler* optimizations; it's much more about *library* optimizations. Some types support extra-efficient ``, `` based on their structures; others do not. `Data.Sequence` gets a major speed boost for every step down (although ` – dfeuer Aug 02 '16 at 15:22
  • @dfeuer Hmmm, that was what I meant with "these instances since they can be simpler than `Monad`". But if you are commenting, then my answer is not obvious. :) I didn't want to over-emphasize this here because when I looked at `()` for `ParsecT`, it didn't seem to be extra efficient compared to `(>>=)`... – Alec Aug 02 '16 at 16:04
  • It may not be! I think most traditional parser combinator libraries are based around parser types that *don't* support especially fast applicative operations. I don't know if there are any that support a monadic interface and that optimize the applicative one. – dfeuer Aug 02 '16 at 16:17
7

Following on from @pigworker (I'm too new on here to comment alas) it's worth noting join $ fM <*> ... <*> ... <*> ... as a pattern as well. It nets you a "bind1, bind2, bind3..." family the same way <$> and <*> get you an "fmap1,fmap2,fmap3" one.

As a stylistic thing, when you're used enough to the combinators it's possible to use do much the same as you would use let: as a way to highlight when you wanted to name something. I tend to want to name things more often in parsers for example, because that probably corresponds to names in the spec!

Will Ness
  • 62,652
  • 8
  • 86
  • 167
  • 2
    Could you expand this to clarify? The first paragraph is rather vague; the second could use some examples. – dfeuer Aug 03 '16 at 07:21