The Lawless Guide to Monads

This is an introduction to the concept of monads in Haskell for those who are starting to get familiar with the basics of the language - things like polymorphism, type signatures, higher-order functions and so. I will use a very simple example that looks useless but will hopefully help you understand this concept.

Start a REPL session with the command ghci and try the following:

ghci> f = (\x -> [2*x])
ghci> f 3
[6]

Here we define a function f that takes a number and gives back a list of number(s). We can use the :type command to check this:

ghci> :type f
f :: Num a => a -> [a]

It can take some time to get use to reading type signatures but this is a fundamental part of programming in Haskell so make sure you understand them.

We will be playing quite a bit with lists so for brevity, let’s create, say, a list of numbers named xs that we can use for trying stuff out:

ghci> xs = [1,2,3]
ghci> :type xs
xs :: Num a => [a]

Since we have a list of numbers and a function that takes a number, we could use the map function to apply f to each element of xs:

ghci> map f xs
[[2],[4],[6]]

As you can see, we have successfully multiplied each element of xs by two, although, because our function returns a list with a single number, we get this funky list of lists of numbers (i.e. Num a => [[a]]). But worry not: as Haskell is such a great language, we can easily flatten it:

ghci> concat (map f xs)
[2,4,6]

If by any chance you are wondering “can we make this shorter?” I’ve got great news for you :D Haskell is such a magnificent language that it provides an operator called “bind” to do the same thing in half the characters!

ghci> xs >>= f
[2,4,6]

Fantastic isn’t it? Next,

ghci> xs >>= (\x -> [2*x])
[2,4,6]
ghci> xs >>= (\x -> return (2*x))
[2,4,6]

The first expression is there just to remind you what f is. Now, the second expression is equivalent to the first but it uses a function named return. Note that this name is unfortunately misleading: this is not a statement for “returning” some value to the caller (like it would be for instance in C), this is just an ordinary function, and in this case we are simply applying it to (2*x).

For the sake of clarity, this is an equivalent expression without using >>=:

concat (map (\x -> return (x*2)) xs)
[2,4,6]

Since we still get the same result, it seems like return is just wrapping a value in a list, no? Hmmm… Let’s check its type signature:

ghci> :type return
return :: Monad m => a -> m a

This says that return is a function that takes a value of some generic type a and gives back a monad of type a. “But weren’t we expecting a list of a?”, I hear you say. Well, guess what? Lists are monads!

You see, for a type to be a monad it needs to implement the functions >>= (bind) and return that work for its own type, and Haskell lists do that out of the box. This idea of classifying something in terms of what you can do with it might seem a bit strange if you don’t have experience with type classes1. If this is the case for you then you may want to revisit ad hoc polymorphism in Haskell to get more familiar with it.

Back to monads, we haven’t checked the signature of >>= yet:

ghci> :type (>>=)
(>>=) :: Monad m => m a -> (a -> m b) -> m b

Since >>= is an infix function, the first parameter represents the left-hand-side, this is m a, and the second one the right-hand-side, (a -> m b). If we consider that in our case m is a list then you could spell out this signature as “bind is a function that takes two parameters: (i) a list of type A and (ii) a function that takes a value of type A and gives back a list of type B; bind then gives back a list of type B”.

If there is one thing you should remember from all this it is this >>= function, in particular its type signature (i.e. m a -> (a -> m b) -> m b). As a mnemonic you could say that “a monad represents a type that can be bound over”, similar to the classic “a functor represents a type that can be mapped over”.

Congrats, you now know what a monad is. There are still a few details2 you could learn but you’ll be alright skipping those for now. In my next article I will show you how to use a monad called IO to deal with I/O operations in a purely functional way.