Haskell/Understanding monads

From Wikibooks, open books for an open world
< Haskell(Redirected from Haskell/Monads)
Jump to: navigation, search

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[1].

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.

Definition[edit]

Let us dive in with both feet. A monad is defined by three things:

  • a type constructor M;
  • a function return[2]; and
  • an operator (>>=) which is pronounced "bind".

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 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.

Motivation: Maybe[edit]

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.

Type class[edit]

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 (>>) 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.

    printSomethingTwice :: String -> IO ()
    printSomethingTwice str = putStrLn str >> putStrLn str

The function 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[edit]

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 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 : 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.

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. 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.


Semantics Monad Wikibook chapter
Exception (anonymous) Maybe Haskell/Understanding monads/Maybe
Exception (with error description) Error Haskell/Understanding monads/Error
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
Logger Writer Haskell/Understanding monads/Writer


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.

Monad Laws[edit]

We can't just allow any junky 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 is expected to obey them.

Return as neutral element[edit]

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[edit]

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 do):

    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)

where (>=>) 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[edit]

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 [3].

So far, we have defined monads in terms of >>= and 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:

  • fmap applies a given function to every element in a container
  • return packages an element into a container,
  • join takes 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 fmap and join in terms of (>>=) and return:

    fmap f x = x >>= (return . f)
    join x   = x >>= id

Is my Monad a Functor?[edit]

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 Monad and 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 fmap. Meanwhile, Control.Monad defines 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 (>>=) and return, just as we have done above. For a properly implemented monad with a matching Functor (that is, pretty much any sensible monad) liftM and fmap are interchangeable.


Notes[edit]

  1. 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!
  2. This return function has nothing to do with the return keyword found in imperative languages like C or Java; don't conflate these two.
  3. Deep into the Advanced Track, we will cover the theoretical side of the story in the chapter on Category Theory.