I have been silent for a while, but I'm not dead (yet). Just navigating the perilous seas of higher level abstractions.
A week or so ago, around midnight (hence the title), I finally managed to understand Monad, Applicative and Functor.
Before the knowledge of my previous unmonadic state flees my mind, as it is custom, I'll write the obligatory Monad Tutorial.
...except that the world has too many of those, so I'll just stick to how I got there. Also I don't have any magic metaphors, and I don't think metaphors help in this case.
Say no to metaphors that only give you the illusion of knowing!
Note: all the code in this article is Purescript, I'll give instructions at the end of the article on how to setup a psci
repl with the needed stuff.
Meet Functor
Some things are contexts (I think of them as containers, boxes):
- Lists:
[1, 2, 3, 4]
- Maybes:
Just 5
- more, but you get my meaning.
What if you want to apply a function to a value inside a context?
> (\n -> n + 5) (Just 5)
HORRIBLE ERROR ENSUES >___<
Enter <$>
, which is the traditional Haskellic squiggle for map
(or fmap
)
> (\n -> n + 5) <$> (Just 5)
(Just 10)
> (\n -> n + 5) <$> Nothing
Nothing
No errors, we get a correct result.
What just happened?
We used a Functor, which applies a function to a value inside a context.
Cheatsheet: | name Functor | infix: <$>
| prefix: map
| in: Data.Functor
Meet Applicative
What if both our function and our value are stored within contexts?
Let's try with our new friend, Functor:
> [(\n -> n + 1), (\n -> n + 2)] <$> [5, 6]
ERROR ERROR
Our friend Functor is not enough, we need another ally: Applicative, represented by the traditional Haskellic squiggle <*>
.
> [(\n -> n + 1), (\n -> n + 2)] <*> [5, 6]
[6,7,7,8]
With Applicative each function in the array was applied to each value in the values array.
We can even generate the functions with <$>
and then apply them with <*>
, instead of writing them by hand:
> ((+) <$> [1,2]) <*> [5, 6]
[6,7,7,8]
An example with a different context:
> ((+) <$> (Just 5)) <*> (Just 3)
(Just 8)
Cheatsheet: | name: Applicative | infix: <*>
| prefix: apply
| in Control.Apply
Meet Monad
If you need to apply a function that returns a value wrapped in a context, to a value which is also wrapped in a context, then it's Monad's turn to shine!
> let half n = if (n `mod` 2 == 0) then Just (n `div` 2) else Nothing
> Just 50 >>= half
Just 25
> Nothing >>= half
Nothing
(btw, this example function is shamelessly stolen from adit.io's great article, as I couldn't think of a better example)
You can chain more of them, which is sort of the point:
> Just 50 >>= half >>= half
Nothing
The do
notation you might have seen around, is syntax sugar for this monad chaining.
Cheatsheet: | name: Monad | infix: >>=
| prefix: bind
| in: Control.Bind
Read more
- If that sort of makes sense, and even more so if it doesn't, go read Adit's enlightening illustrated article.
- Haskell Programming from first principles book
- Purescript by Example book
Words of encouragement, and some tips
-
Don't lose hope. You'll get there.
-
It is mainly a matter of doing enough Strongly Typed Functional Programming, until you encounter the problems that these abstractions solve. Nothing can happen until your brain has that experience.
-
The Elm -> Purescript/Haskell road seems to be working for me so far.
Being used to a good chunk of the syntax from Elm, ADTs, type driven dev, helps lighten the cognitive load and makes you feel the absence of these higher level abstraction in practice. -
Repetita Iuvant: it's pointless to try to understand the Monad until you've experienced the problem that the monad solve. That's why metaphors are not helpful.
-
My Monad learning trigger: I needed a Functor. I needed to apply a function to values inside a
Just
.
Without using a Functor it's annoying and repetitive. Learning what a Functor is, lead to learn Applicative and Monad too. -
What worked for me: I read multiple times chapter 6 to 8 of the Purescript by Example book, and chapter 10 to 19 of the Haskellbook (skipping a lot in that one though). After that I stumbled on adit's excellent post, and Monads finally clicked.
-
Try to memorise what function you need actually to implement in the Typeclass instance (eg.
map
for Functor) -
Monad is the most (in)famous, but I think Functor and Applicative are necessary stepping stones to it, as one can evince from adit's post.
-
'Monad', 'Functor', etc, these names don't really evoke anything, so they're slippery in the mind. I think I forgot them 20 times over and over :P
"Which one is the Applicative, again? Which the Functor? AAAARGH!".
Like that. -
Remember, Maybe is a Functor, and an Applicative too, not just a Monad.
Appendix: Get a Purescript REPL ready
See here to setup Purescript if you haven't already. Then follow these steps:
$ mkdir midnight_monad
$ cd midnight_monad
$ pulp init
$ bower install purescript/purescript-maybe --save
$ bower install purescript/purescript-foldable-traversable --save
$ bower install purescript/purescript-arrays --save
$ pulp psci
PSCi, version 0.9.3
Type :? for help
> import Prelude
> import Data.Array
> import Data.Foldable
> import Data.Maybe
Comments? Give me a shout at @lambda_cat.
To get the latest post updates subscribe to the LambdaCat newsletter.
You can support my writing on LambdaCat's Patreon.