Haskell/Understanding monads
From Wikibooks, the open-content textbooks collection
Monads are a very useful concept in Haskell, but also a relatively difficult one for newcomers. As there are many applications for monads, people have often explained them from a particular point of view, which can make it confusing to understand the other applications of monads.
[edit] Definition
A monad is defined by three things: a type constructor M, a function return[1], and an operator (>>=) which is pronounced "bind". The function and operator 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.
Let's give an example: the Maybe monad. The type constructor is M = Maybe so that return and (>>=) 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.
[edit] Motivation: Maybe
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/mother or return Nothing if they are not stored in the database. With these, 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 functions must fail with Nothing if that happens.
But clearly, there has to be a better way 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.
[edit] Type class
In Haskell, the type class Monad is used to implement monads. It is defined in the Control.Monad module and part of the Prelude:
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 (>>) and fail.
The operator (>>) 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 IO. TODO: [sample code]
The function fail handles pattern match failures in do notation. It is generally considered a wart of the Haskell standard and you can safely ignore it.
[edit] Is my Monad a Functor?
In case you forgot, a functor is a type to which we can apply the fmap function, which is analogous to the map function for lists.
According to category theory, all monads are by definition functors too. However, GHC thinks it different, and the Monad class has actually nothing to do with the Functor class. This will likely change in future versions of Haskell, so that every Monad will have its own fmap; until then, you will have to make two separate instances of your monads (as Monad and as Functor) if you want to use a monad as a functor.
[edit] Notions of Computation
While you probably agree now that (>>=) and 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 grandpas in a very suggestive style:
bothGrandfathers p = do {
f <- father p; -- assign the result of father p to the variable f
gf <- father f; -- similar
m <- mother p; -- ...
gm <- father m; -- ...
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 : father and 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 M. TODO: diagram, picture?
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)
The 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. Also, if the idea of the idea behind monads is still unclear to you, studying each of the following examples in the order suggested will not only give you a well-rounded toolbox but also help you understand the common abstraction behind them.
| Semantics | Monad | Wikibook chapter |
|---|---|---|
| Exceptions | Maybe, Error |
Haskell/Understanding monads/Maybe |
| Global state | State |
Haskell/Understanding monads/State |
| Input/Output | IO |
Haskell/Understanding monads/IO |
| Nondeterminism | [] (lists) |
Haskell/Understanding monads/List |
| Environment | Reader |
Haskell/Understanding monads/Reader |
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.
[edit] Monad Laws
We can't just allow any junkie implementation of (>>=) and 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 in Haskell is expected to obey them.
[edit] 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.
These two laws are in analogy to the laws for the neutral element of a monoid.
[edit] 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.
TODO: exemplify with the two grandpas
Again, this law is analogous to the associativity of a monoid, although it looks a bit different due to the lambda expression (\x -> f x >>= g). There is the alternative formulation as
(f >=> g) >=> h = f >=> (g >=> h)
where (>=>) is the equivalent of function composition (.) for monads and defined as
(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c f >=> g = \x -> f x >>= g
The associativity of the then operator (>>) is a special case:
(m >> n) >> o = m >> (n >> o)
[edit] Footnotes
- ↑ This
returnfunction has nothing to do with thereturnkeyword found in imperative languages like C or Java; don't confound these two.