Monads are very useful in Haskell, but the concept is usually difficult for newcomers to grasp. Since they have so many applications, people often explain them from a particular point of view, which can confuse your understanding of monads in their full glory.
Historically, monads were introduced into Haskell to perform input/output. After all, lazy evaluation means that the order of evaluation is rather unpredictable, whereas a predetermined execution order is crucial for things like reading and writing files. Hence, a method for specifying a sequence of operations was needed, and monads were exactly the right abstraction for that.
But monads are by no means limited to input/output; they can model any imperative language. The choice of monad determines the semantics of this language, i.e., whether it supports exceptions, state, non-determinism, continuations, coroutines and so on.
The present chapter introduces the basic notions with the example of the
Maybe monad, the simplest monad for handling exceptions. Beginning Haskell programmers will probably also want to understand the
IO monad and then broaden their scope to the many other monads and the effects they represent; this chapter provides the corresponding pointers.
Let us dive in with both feet. A monad is defined by three things:
The function and operator are methods of the
Monad type class, have types
return :: a -> M a (>>=) :: M a -> ( a -> M b ) -> M b
and are required to obey three laws that will be explained later on.
For a concrete example, take the
Maybe monad. The type constructor is
M = Maybe so that
(>>=) have types
return :: a -> Maybe a (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
They are implemented as
return x = Just x (>>=) m g = case m of Nothing -> Nothing Just x -> g x
and our task is to explain how and why this definition is useful.
To see the usefulness of
(>>=) and the
Maybe monad, consider the following example: imagine a family database that provides two functions
father :: Person -> Maybe Person mother :: Person -> Maybe Person
that look up the name of someone's father or mother, returning
Nothing if they are not stored in the database. With them, we can query various grandparents. For instance, the following function looks up the maternal grandfather:
maternalGrandfather :: Person -> Maybe Person maternalGrandfather p = case mother p of Nothing -> Nothing Just m -> father m -- mother's father
Or consider a function that checks whether both grandfathers are in the database:
bothGrandfathers :: Person -> Maybe (Person, Person) bothGrandfathers p = case father p of Nothing -> Nothing Just f -> case father f of Nothing -> Nothing Just gf -> -- found first grandfather case mother p of Nothing -> Nothing Just m -> case father m of Nothing -> Nothing Just gm -> -- found second one Just (gf, gm)
What a mouthful! Every single query might fail by returning
Nothing and the whole function must fail with
Nothing if that happens.
Clearly there has to be a better way to write that than repeating the case of
Nothing again and again! Indeed, and that's what the
Maybe monad is set out to do. For instance, the function retrieving the maternal grandfather has exactly the same structure as the
(>>=) operator, and we can rewrite it as
maternalGrandfather p = mother p >>= father
With the help of lambda expressions and
return, we can rewrite the mouthful of two grandfathers as well:
bothGrandfathers p = father p >>= (\f -> father f >>= (\gf -> mother p >>= (\m -> father m >>= (\gm -> return (gf,gm) ))))
While these nested lambda expressions may look confusing to you, the thing to take away here is that
(>>=) eliminates any mention of
Nothing, shifting the focus back to the interesting part of the code.
In Haskell, the type class
Monad is used to implement monads. It is provided by the Control.Monad module and included in the Prelude. The class has the following methods:
class Monad m where return :: a -> m a (>>=) :: m a -> (a -> m b) -> m b (>>) :: m a -> m b -> m b fail :: String -> m a
Aside from return and bind, it defines two additional functions
(>>) called "then" is a mere convenience and commonly implemented as
m >> n = m >>= \_ -> n
It is used for sequencing two monadic actions when the second does not care about the result of the first, which is common for monads like
printSomethingTwice :: String -> IO () printSomethingTwice str = putStrLn str >> putStrLn str
fail handles pattern match failures in
do notation. It's an unfortunate technical necessity and doesn't really have anything to do with monads. You are advised to not call
fail directly in your code.
Notions of Computation
While you probably agree now that
return are very handy for removing boilerplate code that crops up when using
Maybe, there is still the question of why this works and what this is all about.
To answer this, we shall write the example with the two grandfathers in a very suggestive style:
bothGrandfathers p = do f <- father p -- assign p's father to f gf <- father f -- assign f's father (p's paternal grandfather) to gf m <- mother p -- assign p's mother to m gm <- father m -- assign m's father (p's maternal grandfather) to gm return (gf, gm) -- return result pair
If this looks like a code snippet of an imperative programming language to you, that's because it is. In particular, this imperative language supports exceptions :
mother are functions that might fail to produce results, i.e. raise an exception, and when that happens, the whole
do-block will fail, i.e. terminate with an exception.
In other words, the expression
father p, which has type
Maybe Person, is interpreted as a statement of an imperative language that returns a
Person as result. This is true for all monads: a value of type
M a is interpreted as a statement of an imperative language that returns a value of type
a as result; and the semantics of this language are determined by the monad
Now, the bind operator
(>>=) is simply a function version of the semicolon. Just like a
let expression can be written as a function application,
let x = foo in bar corresponds to (\x -> bar) foo
an assignment and semicolon can be written as the bind operator:
x <- foo; bar corresponds to foo >>= (\x -> bar)
return function lifts a value
a to a full-fledged statement
M a of the imperative language.
Different semantics of the imperative language correspond to different monads. The following table shows the classic selection that every Haskell programmer should know. If the idea behind monads is still unclear to you, studying each of the examples in the following subchapters will not only give you a well-rounded toolbox but also help you understand the common abstraction behind them.
|Exception (with error description)||
Furthermore, the semantics do not only occur in isolation but can also be mixed and matched. This gives rise to monad transformers.
Some monads, like monadic parser combinators have loosened their correspondence to an imperative language.
We can't just allow any junky implementation of
return if we want to interpret them as the primitive building blocks of an imperative language. For that, an implementation has to obey the following three laws:
m >>= return = m -- right unit return x >>= f = f x -- left unit (m >>= f) >>= g = m >>= (\x -> f x >>= g) -- associativity
In Haskell, every instance of the
Monad type class is expected to obey them.
Return as neutral element
The behavior of
return is specified by the left and right unit laws. They state that
return doesn't perform any computation, it just collects values. For instance,
maternalGrandfather p = do m <- mother p gm <- father m return gm
is exactly the same as
maternalGrandfather p = do m <- mother p father m
by virtue of the right unit law.
Associativity of bind
The law of associativity makes sure that – just like the semicolon – the bind operator
(>>=) only cares about the order of computations, not about their nesting; e.g. we could have written
bothGrandfathers like this (compare with our earliest version without
bothGrandfathers p = (father p >>= father) >>= (\gf -> (mother p >>= father) >>= (\gm -> return (gf,gm) ))
The associativity of the then operator
(>>) is a special case:
(m >> n) >> o = m >> (n >> o)
It is easier to picture the associativity of bind by recasting the law as
(f >=> g) >=> h = f >=> (g >=> h)
(>=>) is the monad composition operator, a close analogue of the (flipped) function composition operator
(.), defined as
(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c f >=> g = \x -> f x >>= g
Monads and Category Theory
Monads originally come from a branch of mathematics called Category Theory. Fortunately, it is entirely unnecessary to understand category theory in order to understand and use monads in Haskell. However, the definition of monads in Category Theory uses a slightly different presentation. Translated into Haskell, this presentation gives an alternative yet equivalent definition of a monad which can give us some additional insight .
So far, we have defined monads in terms of
return. The alternative definition, instead, starts with monads as functors with two additional combinators:
fmap :: (a -> b) -> M a -> M b -- functor return :: a -> M a join :: M (M a) -> M a
(Reminder: as discussed in the chapter on the functor class, a functor
M can be thought of as container, so that
M a "contains" values of type
a, with a corresponding mapping function, i.e.
fmap, that allows functions to be applied to values inside it.)
Under this interpretation, the functions behave as follows:
fmapapplies a given function to every element in a container
returnpackages an element into a container,
jointakes a container of containers and flattens it into a single container.
With these functions, the bind combinator can be defined as follows:
m >>= g = join (fmap g m)
Likewise, we could give a definition of
join in terms of
fmap f x = x >>= (return . f) join x = x >>= id
Is my Monad a Functor?
At this point we might, with good reason, deduce that all monads are by definition functors as well. While according to category theory that is indeed the case, GHC does it differently, and the
Functor classes are unrelated. That will likely change in future versions of Haskell, so that every
Monad instance will be guaranteed to have a matching
Functor instance and the corresponding
liftM, a function with a strangely familiar type signature...
liftM :: (Monad m) => (a1 -> r) -> m a1 -> m r
As you might suspect,
liftM is merely
fmap implemented with
return, just as we have done above. For a properly implemented monad with a matching
Functor (that is, pretty much any sensible monad)
fmap are interchangeable.
- Indeed, thanks to the versatility of monads, in Haskell none of these constructs is a built-in part of the language; rather, they are defined by the standard libraries!
returnfunction has nothing to do with the
returnkeyword found in imperative languages like C or Java; don't conflate these two.
- Deep into the Advanced Track, we will cover the theoretical side of the story in the chapter on Category Theory.