Prev: Meet the Monads TOC: Contents Next: The monad laws

Doing it with class


Haskell type classes

The discussion in this chapter involves the Haskell type class system. If you are not familiar with type classes in Haskell, you should review them before continuing.

The Monad class

In Haskell, there is a standard Monad class that defines the names and signatures of the two monad functions return and >>=. It is not strictly necessary to make your monads instances of the Monad class, but it is a good idea. Haskell has special support for Monad instances built into the language and making your monads instances of the Monad class will allow you to use these features to write cleaner and more elegant code. Also, making your monads instances of the Monad class communicates important information to others who read the code and failing to do so can cause you to use confusing and non-standard function names. It's easy to do and it has many benefits, so just do it!

The standard Monad class definition in Haskell looks something like this:
class Monad m where
    (>>=)  :: m a -> (a -> m b) -> m b
    return :: a -> m a

Example continued

Continuing the previous example, we will now see how the Maybe type constructor fits into the Haskell monad framework as an instance of the Monad class.

Recall that our Maybe monad used the Just data constructor to fill the role of the monad return function and we built a simple combinator to fill the role of the monad >>= binding function. We can make its role as a monad explicit by declaring Maybe as an instance of the Monad class:
instance Monad Maybe where
    Nothing  >>= f = Nothing
    (Just x) >>= f = f x
    return         = Just

Once we have defined Maybe as an instance of the Monad class, we can use the standard monad operators to build the complex computations:
-- we can use monadic operations to build complicated sequences
maternalGrandfather :: Sheep -> Maybe Sheep
maternalGrandfather s = (return s) >>= mother >>= father

fathersMaternalGrandmother :: Sheep -> Maybe Sheep
fathersMaternalGrandmother s = (return s) >>= father >>= mother >>= mother 

In Haskell, Maybe is defined as an instance of the Monad class in the standard prelude, so you don't need to do it yourself. The other monad we have seen so far, the list constructor, is also defined as an instance of the Monad class in the standard prelude.

When writing functions that work with monads, try to make use of the Monad class instead of using a specific monad instance. A function of the type

doSomething :: (Monad m) => a -> m b
is much more flexible than one of the type
doSomething :: a -> Maybe b
The former function can be used with many types of monads to get different behavior depending on the strategy embodied in the monad, whereas the latter function is restricted to the strategy of the Maybe monad.

Do notation

Using the standard monadic function names is good, but another advantage of membership in the Monad class is the Haskell support for "do" notation. Do notation is an expressive shorthand for building up monadic computations, similar to the way that list comprehensions are an expressive shorthand for building computations on lists. Any instance of the Monad class can be used in a do-block in Haskell.

In short, the do notation allows you to write monadic computations using a pseudo-imperative style with named variables. The result of a monadic computation can be "assigned" to a variable using a left arrow <- operator. Then using that variable in a subsequent monadic computation automatically performs the binding. The type of the expression to the right of the arrow is a monadic type m a. The expression to the left of the arrow is a pattern to be matched against the value inside the monad. (x:xs) would match against Maybe [1,2,3], for example.

Here is a sample of do notation using the Maybe monad:
Code available in example2.hs
-- we can also use do-notation to build complicated sequences
mothersPaternalGrandfather :: Sheep -> Maybe Sheep
mothersPaternalGrandfather s = do m  <- mother s
                                  gf <- father m
                                  father gf
Compare this to fathersMaternalGrandmother written above without using do notation.

The do block shown above is written using the layout rule to define the extent of the block. Haskell also allows you to use braces and semicolons when defining a do block:
mothersPaternalGrandfather s = do { m <- mother s; gf <- father m; father gf }

Notice that do notation resembles an imperative programming language, in which a computation is built up from an explicit sequence of simpler computations. In this respect, monads offer the possibility to create imperative-style computations within a larger functional program. This theme will be expanded upon when we deal with side-effects and the I/O monad later.

Do notation is simply syntactic sugar. There is nothing that can be done using do notation that cannot be done using only the standard monadic operators. But do notation is cleaner and more convenient in some cases, especially when the sequence of monadic computations is long. You should understand both the standard monadic binding notation and do notation and be able to apply each where they are appropriate.

The actual translation from do notation to standard monadic operators is roughly that every expression matched to a pattern, x <- expr1, becomes

expr1 >>= \x ->
and every expression without a variable assignment, expr2 becomes
expr2 >>= \_ ->
All do blocks must end with a monadic expression, and a let clause is allowed at the beginning of a do block (but let clauses in do blocks do not use the "in" keyword). The definition of mothersPaternalGrandfather above would be translated to:
mothersPaternalGrandfather s = mother s >>= \m ->
                               father m >>= \gf ->
                               father gf
It now becomes clear why the binding operator is so named. It is literally used to bind the value in the monad to the argument in the following lambda expression.

Summary

Haskell provides built-in support for monads. To take advantage of Haskell's monad support, you must declare the monad type constructor to be an instance of the Monad class and supply definitions of the return and >>= (pronounced "bind") functions for the monad.

A monad that is an instance of the Monad class can be used with do-notation, which is syntactic sugar that provides a simple, imperative-style notation for describing computations with monads.


Prev: Meet the Monads TOC: Contents Next: The monad laws