Maybe as a Monad
Let's remind ourselves what a monad is: It's a functor m
, that is, a
container. And this container must come equipped with two functions. return
takes a value and produces a container storing this value: return :: a -> m a
.
Given a container x :: m a
and a function f :: a -> m b
, x >>= f
needs to
apply f
to every element in x
and combine the containers of type m b
produced by all these applications of f
into a single container of type m b
.
Here, our container is Maybe
. A container that stores some value x
is
Just x
. Thus,
return x = Just x
or, more succinctly,
return = Just
In the expression x >>= f
, we want to view x
and f
as steps in a
computation. Both steps can fail. The fact that x
can fail is represented by
x
having the type Maybe a
; x
may produce a value of type a
(Just y
),
or it may fail to do so (Nothing
). f
can be seen as continuing the
computation using the value y
produced by x
. If x
fails, then f
has
nothing to work with, so we have no choice but to abort, and the whole
computation x >>= f
fails, is Nothing
. If x
succeeds, then f
does
whatever it does with the value y
produced by x
. f
may itself fail. Hence,
its return type is Maybe b
. The whole computation x >>= f
is still a failure
if f
fails, as in our example where looking up Jane Doe's banner number
succeeded—that's x
—but looking up a grade associated with this banner number
failed—that's f
. If f
succeeds, then the result is whatever result f
produces.
This is exactly the same logic that we used to implement gradyByName
in the
previous subsection, only we replaced the two steps bannerNumberByName
and
gradeByBannerNumber
with two generic steps x
and f
. Thus, we obtain
x >>= f = maybe Nothing f x
Putting it all together, here is our Monad
instance for Maybe
:
instance Monad Maybe where
return = Just
x >>= f = maybe Nothing f x
With this, we can define
gradeByName name = bannerNumberByName name >>= gradeByBannerNumber
or, more succinctly,
gradeByName = bannerNumberByName >=> gradeByBannerNumber
Remember, (>=>)
works for any monad. We implemented it as
(f >=> g) x = f x >>= g
With f = bannerNumberByName
and g = gradeByBannerNumber
, we have
gradeByName name = bannerNumberByName name >>= gradeByBannerNumber -- Definition of gradeByName
= (bannerNumberByName >=> gradeByBannerNumber) name -- Definition of (>=>)
gradeByName = bannerNumberByName >=> gradeByBannerNumber -- Drop common argument name from
-- both sides
Let's try it out. To use (>=>)
, we need to import the Control.Monad
module.
>>> import Control.Monad
>>> gradeByName = bannerNumberByName >=> gradeByBannerNumber
>>> gradeByName "John Doe"
Just "B"
>>> gradeByName "Johnny Doe"
Nothing
>>> gradeByName "Jane Doe"
Nothing
To make double-sure that this definition of a Monad
instance for Maybe
is
sound, let's verify that it satisfies the monad laws. (We'll do this for Maybe
and for the list monad, and I'll leave it as an exercise for you for the other
monads we discuss.) Since we formulated the monad laws in terms of composition
of decorated functions,1 we need to use the implementation of
(>=>)
derived from (>>=)
shown above.
So let's verify the laws:
-
Identity: The identity law says that
return >=> f = f = f >=> return
Let's verify both equalities separately:
(return >=> f) x = return x >>= f -- Definition of (>=>) = maybe Nothing f (return x) -- Definition of (>>=) = maybe Nothing f (Just x) -- Definition of return = case Just x of -- Definition of maybe Nothing -> Nothing Just y -> f y = f x -- Pattern matching return >=> f = f -- Drop the common argument x on both sides
and
(f >=> return) x = f x >>= return -- Definition of (>=>) = maybe Nothing return (f x) -- Definition of (>>=) = maybe Nothing Just (f x) -- Definition of return = case f x of -- Definition of maybe Nothing -> Nothing Just y -> Just y = f x -- Pattern matching f >=> return = f -- Drop the common argument x on both sides
-
Associativity: The associativity law says that
(f >=> g) >=> h = f >=> (g >=> h)
With our definition of
f >=> g
above, we obtain((f >=> g) >=> h) x = (f >=> g) x >>= h -- Definition of (>=>) = (f x >>= g) >>= h -- Definition of (>=>) = maybe Nothing h (f x >>= g) -- Definition of (>>=) = maybe Nothing h (maybe Nothing g (f x)) -- Definition of (>>=) = case f x of -- Definition of maybe and Nothing -> maybe Nothing h Nothing -- inlining of "maybe Nothing h" Just y -> maybe Nothing h (g y) = case f x of Nothing -> case Nothing of -- Definition of maybe Nothing -> Nothing Just z -> h z Just y -> g y >>= h -- Definition of (>>=) = case f x of Nothing -> Nothing -- Pattern matching Just y -> (g >=> h) y -- Definition of (>=>) = maybe Nothing (g >=> h) (f x) -- Definition of maybe = f x >>= (g >=> h) -- Definition of (>>=) = (f >=> (g >=> h)) x -- Definition of (>=>) (f >=> g) >=> h = f >=> (g >=> h) -- Drop the common argument x on both sides
This was a lot of acrobatics to arrive at our definition of return
and (>>=)
for the Maybe
monad and to verify that these definitions do satisfy the monad
laws, but we've gained a lot: Just as we can compose normal functions using
standard function composition, as in k . h . g . f
, we can now easily compose
functions that may fail, as in f >=> g >=> h >=> k
, and the whole computation
automatically aborts and fails as soon as one of the steps in this pipeline
fails. There's no need to check the return value of each individual function
application. The implementation of (>>=)
does that for us.
We would like to do even better and avoid having to generously sprinkle (>=>)
operators throughout our code. do
-notation let's us do this. We'll
discuss it shortly, after figuring out how to catch exceptions as we would in
Java.
-
One can also define the monad laws directly in terms of
(>>=)
or in terms ofjoin
. If you look at the documentation of theMonad
class on Hoogle, you'll find the following laws in terms of(>>=)
:- Left identity:
return x >>= f = f x
- Right identity:
f >>= return = f
- Associativity:
f >>= (\x -> g x >>= h) = (f >>= g) >>= h
I think we can agree that it is much harder to see why these laws are called identity and associativity laws, whereas their translations into laws for
(>=>)
make this really obvious.For completeness, the monad laws in terms of
join
are- Identity:
join . return = id = join . fmap return
- Associativity:
join . fmap join = join . join
This is probably even more obscure, even though it captures exactly the monad laws used in category theory, and all of these laws are equivalent to the ones we stated for
(>=>)
. ↩ - Left identity: