In a recent thread on /r/haskell about how
to motivate the AMP proposal in teaching, I read a comment that finally helped me understand the purpose of Functor
s, Applicative
s and Monad
s.
Wait, what is AMP?
For the reader that hasn’t followed this debacle, the AMP proposal is basically
about making Applicative
a superclass of Monad
. That this hasn’t been so
is often considered one of the historical errors Haskell carries around, since
a Monad
is always an Applicative
.
The hierachy of Functor
, Applicative
and Monad
is thus changed to,
class Functor f where
-- ...
class Functor f => Applicative f where
-- ...
class Applicative m => Monad m where
-- ...
which makes more sense than the arbitrary split before (i.e. Monad
was just
“on its own”).
The concerns surrounding this change has mostly been about
beginner-friendliness, since a Monad
now needs an instance of both
Applicative
and Functor
.
There are various other considerations, which the interested reader can find out more about on the GHC 7.10 migration page or the Haskell wiki entry about AMP.
Back to Functor, Applicative and Monad
I’m mostly going to paraphrase /u/ForTheFunctionGod’s comment (which you can find here), while trying to expand a bit on it with my own understanding.
One can naturally extend the intuition of a Functor
to an Applicative
and
from there to a Monad
.
An explanation of the first two might go as follows:
Let’s say I have a list of numbers and want to add
But what if I had two lists of numbers and wanted to add each number from the first list to each number from the second list? With2
to each of these numbers. You can writefmap (+2) [1..10]
(usingFunctor
).Applicative
you can do just that - it’s likefmap
only it can take multiple arguments. You can write(+) <$> [1,5,10] <*> [11..13]
.
From here, one can motivate Monad
by asking:
What if I wanted to abort midway through - say, if I encountered a problem? You can then write:
= do
add xs ys <- xs
x if x < 0 then []
else do
<- ys
y if y < 0 then []
else return (x+y)
Thus we have the natural hierachy:
Functor
: apply a function to a container.Applicative
: apply a multi-argument function to multiple containers.Monad
: likeApplicative
but I can decide what to do next after each step.
Examples of Functor, Applicative and Monad
While the above may help a bit in the “why?”, there is still the question of how to use them. I won’t go into much detail since there exists a wealth of information on this topic already (you can quickly google them), but just give some brief examples of what happens when using them.
Functor
is the simplest, and can be thought of as a much more general map
. Wherever you use map
you can always replace it with fmap
, but not the other way around.
> fmap (*2) [1..10]
2,4,6,8,10,12,14,16,18,20] [
fmap
simply takes every element of the list and applies the function to it.
Applicative
on the other hand is a bit more tricky to understand. The
best starting point is probably to show where fmap
is not enough.
Imagine we want to apply the function *
(multiplication) to a list, we could
try fmap (*) [1..3]
but that would give us a bunch of partially applied
functions back, like [(*1), (*2), (*3)]
.
Now, what can we do with this? We can for example map a value onto the list of
partially applied functions, which would look something like this using fmap
,
> let a = fmap (*) [1..3]
> fmap (\f -> f 9) a
9,18,27] [
which can be seen as applying a function that takes a function as argument an
applies 9
to that function, to every function in the list a
. The real problem
comes when we want to apply a Functor
function to Functor
values, but I won’t
get into much detail on that (you can read more here).
Luckily, instead of writing the above, we can use <*>
which is a part of
Applicative
. We can instead write,
> fmap (*) [1..3] <*> [9]
9,18,27] [
Note that the reason we put 9
inside a context (here a list), is because
Applicative
expects everything to be a Functor
. Since the first part is so
common, Control.Applicative
actually exports <$>
, so we can do the
following instead, replacing fmap
,
> (*) <$> [1..3] <*> [9]
9,18,27] [
Admittedly, it’s a bit more interesting when we want to apply the multiple functions to multiple arguments, as such,
> (*) <$> [1,5,10] <*> [11..13]
11,12,13,55,60,65,110,120,130]
[> (*) <$> Just 3 <*> Just 5
or perhaps inside contexts, such as Maybe
,
> (*) <$> Just 3 <*> Just 5
Just 15
but I won’t go into more detail about that, since that isn’t the purpose of this post.
Monad
is the last, but perhaps most tricky. I’ll try to be as brief as
possible though. Too see the “what happens next, based on what happened before”
part, we will look into the Maybe
monad used with do
notation. It will not
explain Monad
usage in general, but should be enough to get the gist that
“something” happens between the steps.
For example (note that I use ; instead of linebreaks because we are executing in the GHCi REPL),
> do Just 6; Nothing; Just 9
Nothing
You may be aware that a do
block returns the last line executed in it - so
why did it here return Nothing
when the last line was Just 9
? That is
because on each step the bind function >>=
(or =<<
for the other direction) is applied. The Maybe
Monad
defines that if it encounters a Nothing
then every subsequent actions also return a Nothing
.
To understand this better, let’s take a look at the Maybe
instance,
instance Monad Maybe where
return x = Just x
Nothing >>= f = Nothing
Just x >>= f = f x
fail _ = Nothing
Notably here is the line that says Nothing >>= f = Nothing
. It throws away
whatever future action it gets and just returns Nothing
. This is in
contrast to Just x >>= f = f x
which can be seen as unpacking x
from Just x
and then applying the future action, f
, to that x
.
Wrap up
Hopefully this should have helped understand the difference of Functor
,
Applicative
and Monad
a bit, and how they work together. To understand
these concepts more in depth, I recommend reading a bit up on them, and
especially for Monad
, try reading about the Writer
and State
Monad
s,
how they are used and how they are implemented. One resource for that is the
chapter on a few more monads in LYAH.
If you are confused about “What exactly is a Functor, is it a class or interface or whatever?”, then I gave my take on it here. Other than that, I encourage that you read a bit on Type Class’.
Finally, I can deeply recommend the book Haskell Programming - from first principle. It is still in development as of this date, but already features 700 pages of quality content. Simply the best Haskell book I have ever read (or, I’m still reading it as of writing).