Prev: Doing it with class TOC: Contents Next: Exercises

The monad laws


The tutorial up to now has avoided technical discussions, but there are a few technical points that must be made concerning monads. Monadic operations must obey a set of laws, known as "the monad axioms". These laws aren't enforced by the Haskell compiler, so it is up to the programmer to ensure that any Monad instances he declares obey they laws. Haskell's Monad class also includes some functions beyond the minimal complete definition that we have not seen yet. Finally, many monads obey additional laws beyond the standard monad laws, and there is an additional Haskell class to support these extended monads.

The three fundamental laws

The concept of a monad comes from a branch of mathematics called category theory. While it is not necessary to know category theory to create and use monads, we do need to obey a small bit of mathematical formalism. To create a monad, it is not enough just to declare a Haskell instance of the Monad class with the correct type signatures. To be a proper monad, the return and >>= functions must work together according to three laws:

  1. (return x) >>= f == f x
  2. m >>= return == m
  3. (m >>= f) >>= g == m >>= (\x -> f x >>= g)

The first law requires that return is a left-identity with respect to >>=. The second law requires that return is a right-identity with respect to >>=. The third law is a kind of associativity law for >>=. Obeying the three laws ensures that the semantics of the do-notation using the monad will be consistent.

Any type constructor with return and bind operators that satisfy the three monad laws is a monad. In Haskell, the compiler does not check that the laws hold for every instance of the Monad class. It is up to the programmer to ensure that any Monad instance he creates satisfies the monad laws.

Failure IS an option

The definition of the Monad class given earlier showed only the minimal complete definition. The full definition of the Monad class actually includes two additional functions: fail and >>.

The default implementation of the fail function is:
fail s = error s
You do not need to change this for your monad unless you want to provide different behavior for failure or to incorporate failure into the computational strategy of your monad. The Maybe monad, for instance, defines fail as:
fail _ = Nothing
so that fail returns an instance of the Maybe monad with meaningful behavior when it is bound with other functions in the Maybe monad.

The fail function is not a required part of the mathematical definition of a monad, but it is included in the standard Monad class definition because of the role it plays in Haskell's do notation. The fail function is called whenever a pattern matching failure occurs in a do block:
fn :: Int -> Maybe [Int]
fn idx = do let l = [Just [1,2,3], Nothing, Just [], Just [7..20]]
            (x:xs) <- l!!idx   -- a pattern match failure will call "fail"
            return xs
So in the code above, fn 0 has the value Just [2,3], but fn 1 and fn 2 both have the value Nothing.

The >> function is a convenience operator that is used to bind a monadic computation that does not require input from the previous computation in the sequence. It is defined in terms of >>=:
(>>) :: m a -> m b -> m b
m >> k = m >>= (\_ -> k)

No way out

You might have noticed that there is no way to get values out of a monad as defined in the standard Monad class. That is not an accident. Nothing prevents the monad author from allowing it using functions specific to the monad. For instance, values can be extracted from the Maybe monad by pattern matching on Just x or using the fromJust function.

By not requiring such a function, the Haskell Monad class allows the creation of one-way monads. One-way monads allow values to enter the monad through the return function (and sometimes the fail function) and they allow computations to be performed within the monad using the bind functions >>= and >>, but they do not allow values back out of the monad.

The IO monad is a familiar example of a one-way monad in Haskell. Because you can't escape from the IO monad, it is impossible to write a function that does a computation in the IO monad but whose result type does not include the IO type constructor. This means that any function whose result type does not contain the IO type constructor is guaranteed not to use the IO monad. Other monads, such as List and Maybe, do allow values out of the monad. So it is possible to write functions which use these monads internally but return non-monadic values.

The wonderful feature of a one-way monad is that it can support side-effects in its monadic operations but prevent them from destroying the functional properties of the non-monadic portions of the program.

Consider the simple issue of reading a character from the user. We cannot simply have a function readChar :: Char, because it needs to return a different character each time it is called, depending on the input from the user. It is an essential property of Haskell as a pure functional language that all functions return the same value when called twice with the same arguments. But it is ok to have an I/O function getChar :: IO Char in the IO monad, because it can only be used in a sequence within the one-way monad. There is no way to get rid of the IO type constructor in the signature of any function that uses it, so the IO type constructor acts as a kind of tag that identifies all functions that do I/O. Furthermore, such functions are only useful within the IO monad. So a one-way monad effectively creates an isolated computational domain in which the rules of a pure functional language can be relaxed. Functional computations can move into the domain, but dangerous side-effects and non-referentially-transparent functions cannot escape from it.

Another common pattern when defining monads is to represent monadic values as functions. Then when the value of a monadic computation is required, the resulting monad is "run" to provide the answer.

Zero and Plus

Beyond the three monad laws stated above, some monads obey additional laws. The monads have a special value mzero and an operator mplus that obey four additional laws:

  1. mzero >>= f == mzero
  2. m >>= (\x -> mzero) == mzero
  3. mzero `mplus` m == m
  4. m `mplus` mzero == m

It is easy to remember the laws for mzero and mplus if you associate mzero with 0, mplus with +, and >>= with × in ordinary arithmetic.

Monads which have a zero and a plus can be declared as instances of the MonadPlus class in Haskell:
class (Monad m) => MonadPlus m where
    mzero :: m a
    mplus :: m a -> m a -> m a

Continuing to use the Maybe monad as an example, we see that the Maybe monad is an instance of MonadPlus:
instance MonadPlus Maybe where
    mzero             = Nothing
    Nothing `mplus` x = x
    x `mplus` _       = x
This identifies Nothing as the zero value and says that adding two Maybe values together gives the first value that is not Nothing. If both input values are Nothing, then the result of mplus is also Nothing.

The List monad also has a zero and a plus. mzero is the empty list and mplus is the ++ operator.

The mplus operator is used to combine monadic values from separate computations into a single monadic value. Within the context of our sheep-cloning example, we could use Maybe's mplus to define a function, parent s = (mother s) `mplus` (father s), which would return a parent if there is one, and Nothing is the sheep has no parents at all. For a sheep with both parents, the function would return one or the other, depending on the exact definition of mplus in the Maybe monad.

Summary

Instances of the Monad class should conform to the so-called monad laws, which describe algabraic properties of monads. There are three of these laws which state that the return function is both a left and a right identity and that the binding operator is associative. Failure to satisfy these laws will result in monads that do not behave properly and may cause subtle problems when using do-notation.

In addition to the return and >>= functions, the Monad class defines another function, fail. The fail function is not a technical requirement for inclusion as a monad, but it is often useful in practice and it is included in the Monad class because it is used in Haskell's do-notation.

Some monads obey laws beyond the three basic monad laws. An important class of such monads are ones which have a notion of a zero element and a plus operator. Haskell provides a MonadPlus class for such monads which define the mzero value and the mplus operator.


Prev: Doing it with class TOC: Contents Next: Exercises