Skip to content

Instantly share code, notes, and snippets.

@gatlin
Created November 25, 2012 21:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gatlin/4145341 to your computer and use it in GitHub Desktop.
Save gatlin/4145341 to your computer and use it in GitHub Desktop.
My explanation of monads
It's said that all Haskell programmers eventually write a blog post explaining
Monads. Today I fulfill my destiny. The basic format is ripped from "Learn You A
Haskell For Great Good," but these are my own crappy examples.
This blog post is literate Haskell; you can get it
[here](https://gist.github.com/4145341). You can import it into ghci and type
the functions to see their output (`ghci monads.lhs`). For this to work, we'll
need to import one module for use later:
> import Control.Applicative
A *functor* generalizes the notion of types to a type with context. For example,
the Maybe type is literally this:
Maybe a = Just a | Nothing
It wraps a simple type into the notion of uncertainty, so Maybe is a functor.
The list type is really `List a` is a list, but you can think of lists as
functors generalizing types into the notion of non-determinism. To be a functor,
a type must implement `fmap` which has a type signature of `(a -> b) -> f a -> f
b`. Or concretely:
> example1 = fmap (*2) (Just 4)
Just 8
An *applicative functor* takes this further and generalizes the notion of
function application inside a context. In addition to `fmap`, a type that wants
to be an applicative functor must implement `<*> :: (a -> b) -> f a -> f b`. The
only difference between it and `fmap` is that the first argument, the function
to be applied, must be inside the context already. Take a breath and read this
example:
> even? x
> | x `mod` 2 == 0 = True
> | otherwise = False
> example2 = (even?) <$> [1..12] -- <$> is just fmap, but sexier to go with <*>
[False,True,False,True,False,True,False,True,False,True,False,True]
Note that Haskell gives Lists special treatment because they're so fundamental.
So we write `[a]` instead of `List a` and, though we didn't show it here, `fmap`
is just the normal `map` function you expect.
> example3 = (*) <$> [1,2] <*> [3,4,5]
[4,5,6,5,6,7]
The result of applying `(*)` to `1` is a function that accepts one argument and
returns the product. `<$>` will return this new function wrapped in the List
context and `<*>` can then apply this contextualized function to `[3,4,5]`, in
order.
> example4 = [ (*1), (*2) ] <*> [1..4]
[1,2,3,4,2,4,6,8]
Note the length of the resulting lists is the product of the length of each
list. One more to drive that point home:
> example5 = [ (*x) | x <- [1..3] ] <*> [1..4]
[1,2,3,4,2,4,6,8,3,6,9,12]
Where functors generalize types inside contexts, and applicative functors
generalize application inside contexts, *monads* generalize **composition**
inside contexts.
A monad must implement `>>= :: m a -> (a -> mb) -> m b`, which you pronounce as
"bind." It takes a value wrapped in a monad context (`m`) and a function from a
non-monadic value to a monadic value, and applies them. The result is something
that can be used all over again in the same way. Example!
> example6 = ((take 5) . repeat) 2 -- signature is just a -> [a]
[2,2,2,2,2]
`example6` returns a List of values, so it returns a monad.
> example7 = [2,3] >>= ((take 5) . repeat)
[2,2,2,2,2,3,3,3,3,3]
Haskell has a special notation for monad computations called do-notation. It
should illuminate the relationship between imperative programming and monads:
> example8 = do
> l <- [2,3]
> ((take 5) . repeat) l
[2,2,2,2,2,3,3,3,3,3]
But let's focus on what just happened: I took a list of values and a function to
act on a single value and applied it to the entire list *without explicit
iteration.*
wtfbbqomg.
That's the magic of monads, really. A monad is a polymorphic type that
generalizes the notion of function composition, and because of this you can do
interesting things *during* composition. The Maybe monad does similar things:
> ensureEven x
> | x `mod` 2 == 0 = Just x
> | otherwise = Nothing
> example9 = do
> a <- ensureEven 2
> b <- ensureEven $ a * 2;
> return b
Just 4
> example10 = do
> a <- ensureEven 2
> b <- ensureEven $ a + 1
> return b
Nothing
So, a summary:
* Functors generalize types;
* Applicative functors generalize application;
* Monads generalize composition.
The point of generalization is to let you assign some interesting or novel
meaning to these abstract ideas. List models non-determinism; Maybe models
simple uncertainty; IO models, uh, IO actions.
That's why it kind of misses the point to say "monads model state." Certainly
they can, but what they really do is so much more powerful. Here is `example10`
without the do-notation:
> example11 = (ensureEven 2) >>=
> (\a -> ensureEven (a + 1) >>=
> (\b -> return b))
The result of `ensureEven 2` is fed into a single function: an anonymous
function with a free variable `a`. It in turn contains an inner anonymous
function `(\b -> return b)`, which, thanks to the magic of lexical scope, can
see `a`. And that's why
> example12 = do
> putStrLn "Enter some text:"
> input <- getLine
> return input
can safely perform IO: the IO monad generalizes composition such that functions
are called in the order they need to be in the real world (ie, asking for text
before listening for it) and the "variables" can't be shared or mutated without
you knowing.
Hope this made some sense.
@csoroz
Copy link

csoroz commented Apr 9, 2019

example3 = (*) <$> [1,2] <*> [3,4,5]
[3,4,5,6,8,10]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment