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.
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,
1
2 -- ...
3
4
5 -- ...
6
7
8 -- ...
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.
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
2
to each of these numbers. You can writefmap (+2) [1..10]
(usingFunctor
).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? With
Applicative
you can do just that - it's likefmap
only it can take multiple arguments. You can write(+) <$> [1,5,10] <*> [11..13]
.1
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:1
1 add xs ys = do
2 x <- xs
3 if x < 0 then []
4 else do
5 y <- ys
6 if y < 0 then []
7 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
: like Applicative
but I can decide what to do next after each step.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,
1
2 return x = Just x
3 Nothing >>= f = Nothing
4 Just x >>= f = f x
5 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
.
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).
https://www.reddit.com/r/haskell/comments/3tpom7/amp_how_do_you_motivate_this_in_teaching/cx8an8b