It seems to me all these concepts actually have something in common; they are all about function application. Say we have a “normal” function
f :: a -> b, we can apply it to an argument of type
a and get back something of type
Sometimes we don’t have a plain
a, instead we have an
a with some additional context or information around it. Say a list
Maybe a (which can hold a single
a or nothing). We’ve got all these functions that already work on
a -> b, so it would be nice to apply them to
a’s in other contexts.
One way to do this is with functors. Types that are instances of
Functor all implement
fmap :: Functor f => (a -> b) -> f a -> f b
This lets us map the function inside our context, and returns a result inside the same context.
ghci> fmap (+1) [1..5] [2,3,4,5,6] ghci> fmap (+1) (Just 2) Just 3
In these examples we’re applying the function
(+1) over each element in a list, and inside the context of a
Functors let us apply functions inside a context.
What if our function
a -> b is in a particular context as well, like a list of functions? We can apply these using applicative functors, which define an apply function denoted as
(<*>) :: Applicative f => f (a -> b) -> f a -> f b ghci> [(+1), (+2), (*10)] <*> [1,2,3] [2,3,4,3,4,5,10,20,30] ghci> Just (+1) <*> (Just 4) Just 5
This turns out to be very useful, as it lets us apply functions with more than one argument inside a context, where standard functors only really helped with single argument functions.
ghci> Just (+) <*> Just 10 <*> Just 20 Just 30 ghci> Just (+) <*> [1,2] <*> [3,4] [4,5,5,6]
For the first example, we put the
(+) function into a
Just (+). We apply the first argument
Just 10 using
<*> to give us
Just (10+), then applies the second argument to get our answer of
Applicative functors let us apply functions with multiple arguments to arguments inside a context.
And so we come to monads. Monads define a few functions, the main ones being bind, denoted as
return which puts a value into the right context:
(>>=) :: Monad m => m a -> (a -> m b) -> m b return :: Monad m => a -> m a
This allows us to apply a function to an
m a (an
a in some context) that uses that
a value to produce an
m b; a new
b in the same context. Any type that works with functions with these signatures (and obeys a couple of rules) are monads.
ghci> Just 42 >>= (\x -> return (x+2)) Just 44
Here we’ve bound
Just 42 to a function. The bind operation will take the
42 out of the
Maybe context and pass it into the bound function as
x. This function uses
return (x+2) to add 2 and put the result back into the
Maybe context, which gives us
What does this give us which
fmap (+2) (Just 42) won’t? The ability to chain, or compose, several of these bindings, and potentially handle failures along the way:
Here we’ve composed several functions together that work in the
Maybe context, producing a tuple that depends on the previous
y values, all within the
Maybe context. If one of the bindings produces
Nothing, the whole expression reduces to
In other words:
Monads let us apply functions inside a context which depend on previous results
As a quick aside, Haskell also lets chain bind operations using do-notation, which is generally more readable than chaining
>>= calls. Haskell will translate the example below into the same calls to
>>= as our example above.
Seriously? That’s it?
A monad is another abstraction, like functors and applicative functors, to let us apply functions to values in various contexts. If your type works with a function with a bind-like signature,
m a -> (a -> m b) -> m b, then there’s a good chance you’ve got a monad.
If you’ve used LINQ in .NET, you may be familiar with this method:
In other words, this method takes an
IEnumerable<A> and a function which takes an
A and returns an
IEnumerable<B>, and then returns an
-- Pseudo-code ahead! SelectMany :: IEnumerable<A> -> (A -> IEnumerable<B>) -> IEnumerable<B> (>>=) :: m a -> (a -> m b) -> m b
SelectMany is the bind operation for the
IEnumerable, or collection, monad. While we’re at it, the more common
Select method corresponds to
fmap (all monads happen to be functors as well).
Why the fuss?
So answering the question “what is a monad?” is not too difficult; it’s a type which has an appropriate bind function. Understanding all the implications of this is a much more involved task, and one I definitely haven’t got a handle on yet. Here’s my interpretation so far.
We can use the bind function to produce a new value that depends on a chain of previous values. This lets us evaluate a sequence of expressions, giving us a type-safe, functional alternative to the sequences of steps used in imperative programming. If the previous values we’re working with include a representation of the state at that time (say a tuple
(value, state)) then we have a very nice way of modelling stateful computations, with each new value and state depending on the previous ones (this is called the state monad).
Monads also let us use values without leaving a particular context. In our
Maybe examples we could work on the values within our monad (like
Just 42), and get a result that is still in that context (
Just (70,14)). This is useful for functions with side-effects, or impure functions, such as those that work with IO. We can perform operations on the results of IO actions without having a value from an impure function floating around the rest of our pure program.
Another thing monads give us is the potential to handle errors or otherwise stop evaluation early. We saw an example of this when one of our
Maybe values was
Nothing, and the entire expression evaluated to
Nothing rather than raising an error or trying to continue evaluation. Contrast this to how imperative programs normally react when they get an unexpected
null (cue null reference exception).
There are lots of implementations of monads, such as Reader, Writer, State, Maybe, list, IO, and parsers. These all have their own interesting characteristics, thanks to their particular data structures and implementations of bind. Learn You A Haskell goes through some examples of some of these monads. These specific monad instances give more concrete examples of what the abstract monad concept can be used to achieve.
Just like functors and applicatives, monads seem another way of applying functions to values within certain contexts, such as in a list or a
Maybe. The monad bind operation,
(>>=) :: m a -> (a -> m b) -> m b lets us chain or compose these function applications in a way that produces a result that depends on the previous values, all without leaving the context of the monad.
While working out the answer to the question “what is a monad?” wasn’t too painful (I hope), understanding the implications of this abstract concept is more difficult. Monads can help us sequence operations, model state, or safely short-circuit evaluation under certain conditions like errors. Specific examples like State, Writer and list give us some idea as to how the concept can be applied, but I’m going to need a lot more practice before I start getting a comprehensive understanding of the myriad uses of monads.