In one discussion I heard that Applicative
interface of some parsers is implemented differently, more efficiently than their Monad
interface. The reason is that with Applicative
we know all "effects" in advance, before the whole effectful computation is run. With monads, effects can depend on values during the computation so this optimization is not possible.
I'd like to see some good examples of this. It can be some very simple parser or some different monad, that's not important. The important thing is that the Applicative
interface of such a monad complies with its return
and ap
, but using the Applicative
produces more efficient code.
Update: Just to clarify, here I'm not interested in applicatives that can't be monads. The question is about things that are both.
Another example is a strict left fold. You can write an applicative instance which allows you to compose folds so that the resulting fold can be performed on the data in a single pass and constant space. However, the monad instance needs to re-iterate from the beginning of the data for each bind and keep the whole list in memory.
I wrote the above from the top of my head as an example so it might not be the optimal implementation for applicative and monadic folds, but running the above gives me:
Perhaps the canonical example is given by the vectors.
We can make them applicative with a little effort, first defining singletons, then wrapping them in a class.
Now we may develop the
Applicative
structureI omit the
Functor
instance (which should be extracted viafmapDefault
from theTraversable
instance).Now, there is a
Monad
instance corresponding to thisApplicative
, but what is it? Diagonal thinking! That's what's required! A vector can be seen as the tabulation of a function from a finite domain, hence theApplicative
is just a tabulation of the K- and S-combinators, and theMonad
has aReader
-like behaviour.You might save a bit by defining
>>=
more directly, but any way you cut it, the monadic behaviour creates useless thunks for off-diagonal computations. Laziness might save us from slowing down by an armageddon factor, but the zipping behaviour of the<*>
is bound to be at least a little cheaper than taking the diagonal of a matrix.As pigworker said, arrays are the obvious example; their monad instance is not just a bit more problematic on the conceptual level with type-indexed lengths etc., but also performs worse in the very much real-worldly
Data.Vector
implementation:Note that it doesn't come out with the performance difference when you compile with
-O2
; apparentlyap
is replaced by<*>
then. But>>=
can only allocate the right amount of memory after each function call and then put the results in place, which appears to be quite time-expensive; whereas<*>
can simply precompute the result length as the product offunctions
andvalues
lengths, and then write to one fixed array.