Haskell/Understanding monads

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

Monads are very useful in Haskell, but the concept is usually relatively difficult for newcomers to understand. 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 hyperlinks.

Definition [edit]

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

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

Type class [edit]

In Haskell, the type class Monad is used to implement monads. It is defined in the Control.Monad module and as 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.

    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 to do anything with monads. You are advised to not call fail directly in your code.

Is my Monad a Functor? [edit]

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

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 grandpas 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. 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 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 simply the equivalent of (flipped) function composition (.) for monads, 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. When translated into Haskell, this presentation gives a different, but equivalent definition of a monad which may be useful to understand.

So far, we have defined monads in terms of >>= and return, but there's also an alternative definition that 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

A functor M can be thought of as container, so that M a "contains" values of type a.

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 turns them into a single container.

With these functions, the bind combinator is defined as follows:

    m >>= g = join (fmap g m)

Likewise, we could give a definition of fmap and join in terms of >>=:

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

For more information on this point of view, see also the wikibook chapter on Category Theory.

Notes [edit]

  1. Indeed, thanks to the versatility of monads, in Haskell all of these constructs are not a built-in part of the language, but rather defined by 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.