I just read the following from typeclassopedia about the difference between Monad
and Applicative
. I can understand that there is no join
in Applicative
. But the following description looks vague to me and I couldn't figure out what exactly is meant by "the result" of a monadic computation/action. So, if I put a value into Maybe
, which makes a monad, what is the result of this "computation"?
Let’s look more closely at the type of (>>=). The basic intuition is that it combines two computations into one larger computation. The first argument, m a, is the first computation. However, it would be boring if the second argument were just an m b; then there would be no way for the computations to interact with one another (actually, this is exactly the situation with Applicative). So, the second argument to (>>=) has type a -> m b: a function of this type, given a result of the first computation, can produce a second computation to be run. ... Intuitively, it is this ability to use the output from previous computations to decide what computations to run next that makes Monad more powerful than Applicative. The structure of an Applicative computation is fixed, whereas the structure of a Monad computation can change based on intermediate results.
Is there a concrete example illustrating "ability to use the output from previous computations to decide what computations to run next", which Applicative does not have?
Just 1
describes a "computation", whose "result" is 1.Nothing
describes a computation which produces no results.The difference between a Monad and an Applicative is that in the Monad there's a choice. The key distinction of Monads is the ability to choose between different paths in computation (not just break out early). Depending on a value produced by a previous step in computation, the rest of computation structure can change.
Here's what this means. In the monadic chain
the
if
chooses what computation to construct.In case of Applicative, in
all the functions work "inside" computations, there's no chance to break up a chain. Each function just transforms a value it's fed. The "shape" of the computation structure is entirely "on the outside" from the functions' point of view.
A function could return a special value to indicate failure, but it can't cause next steps in the computation to be skipped. They all will have to process the special value in a special way too. The shape of the computation can not be changed according to received value.
With monads, the functions themselves construct computations to their choosing.
Here is my take on @J. Abrahamson's example as to why
ifA
cannot use the value inside e.g.(pure True)
. In essence, it still boils down to the absence of thejoin
function fromMonad
inApplicative
, which unifies the two different perspectives given in typeclassopedia to explain the difference betweenMonad
andApplicative
.So using @J. Abrahamson's example of purely applicative
Either
:(which has similar short-circuiting effect to the
Either
Monad
), and theifA
functionWhat if we try to achieve the mentioned equations:
?
Well, as already pointed out, ultimately, the content of
(pure True)
, cannot be used by a later computation. But technically speaking, this isn't right. We can use the content of(pure True)
since aMonad
is also aFunctor
withfmap
. We can do:The problem is with the return type of
ifA'
, which isf (f a)
. InApplicative
, there is no way of collapsing two nestedApplicative
S into one. But this collapsing function is precisely whatjoin
inMonad
performs. So,will satisfy the equations for
ifA
, if we can implementjoin
appropriately. WhatApplicative
is missing here is exactly thejoin
function. In other words, we can somehow use the result from the previous result inApplicative
. But doing so in anApplicative
framework will involve augmenting the type of the return value to a nested applicative value, which we have no means to bring back to a single-level applicative value. This will be a serious problem because, e.g., we cannot compose functions usingApplicative
S appropriately. Usingjoin
fixes the issue, but the very introduction ofjoin
promotes theApplicative
to aMonad
.I would like to share my view on this "iffy miffy" thing, as I understand this everything inside the context get applied, so for example:
upps should be Just "True" ... but
(the "good" choice is made inside the context) I explained this to myself this way, just before the end of the computation in case >>1 we get something like that in the "chain" :
which according by definition of Applicative is evaluated as:
which when "something" is Nothing becomes a Nothing according to Functor constraint (fmap over Nothing gives Nothing). And it is not possible to define a Functor with "fmap f Nothing = something" end of story.
Well, that vagueness is somewhat deliberate, because what "the result" is of a monadic computation is something that depends on each type. The best answer is a bit tautological: the "result" (or results, since there can be more than one) is whatever value(s) the instance's implementation of
(>>=) :: Monad m => m a -> (a -> m b) -> m b
invokes the function argument with.The
Maybe
monad looks like this:The only thing in here that qualifies as a "result" is the
a
in the second equation for>>=
, because it's the only thing that ever gets "fed" to the second argument of>>=
.Other answers have gone into depth about the
ifA
vs.ifM
difference, so I thought I'd highlight another significant difference: applicatives compose, monads don't. WithMonad
s, if you want to make aMonad
that combines the effects of two existing ones, you have to rewrite one of them as a monad transformer. In contrast, if you have twoApplicatives
you can easily make a more complex one out of them, as shown below. (Code is copypasted fromtransformers
.)Now, if we add in the
Constant
functor/applicative:...we can assemble the "applicative
Either
" from the other responses out ofLift
andConstant
:My favorite example is the "purely applicative Either". We'll start by analyzing the base Monad instance for Either
This instance embeds a very natural short-circuiting notion: we proceed from left to right and once a single computation "fails" into the
Left
then all the rest do as well. There's also the naturalApplicative
instance that anyMonad
haswhere
ap
is nothing more than left-to-right sequencing before areturn
:Now the trouble with this
Either
instance comes to light when you'd like to collect error messages which occur anywhere in a computation and somehow produce a summary of errors. This flies in the face of short-circuiting. It also flies in the face of the type of(>>=)
If we think of
m a
as "the past" andm b
as "the future" then(>>=)
produces the future from the past so long as it can run the "stepper"(a -> m b)
. This "stepper" demands that the value ofa
really exists in the future... and this is impossible forEither
. Therefore(>>=)
demands short-circuiting.So instead we'll implement an
Applicative
instance which cannot have a correspondingMonad
.Now the implementation of
(<*>)
is the special part worth considering carefully. It performs some amount of "short-circuiting" in its first 3 cases, but does something interesting in the fourth.Notice again that if we think of the left argument as "the past" and the right argument as "the future" then
(<*>)
is special compared to(>>=)
as it's allowed to "open up" the future and the past in parallel instead of necessarily needing results from "the past" in order to compute "the future".This means, directly, that we can use our purely
Applicative
Either
to collect errors, ignoringRight
s if anyLeft
s exist in the chainSo let's flip this intuition on its head. What can we not do with a purely applicative
Either
? Well, since its operation depends upon examining the future prior to running the past, we must be able to determine the structure of the future without depending upon values in the past. In other words, we cannot writewhich satisfies the following equations
while we can write
ifM
such that
This impossibility arises because
ifA
embodies exactly the idea of the result computation depending upon the values embedded in the argument computations.The key of the difference can be observed in the type of
ap
vs type of=<<
.In both cases there is
m a
, but only in the second casem a
can decide whether the function(a->m b)
gets applied. In its turn, the function(a->m b)
can "decide" whether the function bound next gets applied - by producing suchm b
that does not "contain"b
(like[]
,Nothing
orLeft
).In
Applicative
there is no way for functions "inside"m (a->b)
to make such "decisions" - they always produce a value of typeb
.In
Applicative
this is not possible, so can't show a example. The closest is: