| Prev: Doing it with class | TOC: Contents | Next: Exercises |
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 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:
(return x) >>= f == f xm >>= return == m(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.
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 |
Maybe
monad, for instance, defines fail as:
fail _ = Nothing |
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
|
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) |
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.
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:
mzero >>= f == mzerom >>= (\x -> mzero) == mzeromzero `mplus` m == mm `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
|
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.
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 |