MaybeT
Our implementation of display
in the previous subsection simply aborts if the
command line arguments aren't what we expected or the file to be display cannot
be read. This is exactly what we normally implement using the Maybe
monad. We
can't quite use the Maybe
monad because our implementation of display
needs
to IO
monad to read command line arguments, read the file to be displayed, and
either print the file or pass it to less
. In the previous subsection, we
simply used solved this problem by explictly checking return values. In this
section, we will use the MaybeT
monad transformer to equip the IO
monad with
the abortion logic of the Maybe
monad:
newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }
Instead of simply a value of type Maybe a
, MaybeT
is a wrapper around an
action in the monad m
that returns a value of type Maybe a
. Let's figure out
how we turn this into a monad. The logic of our Maybe
monad was that a
sequence of steps aborts as soon as the first step in such a computation aborts.
It succeeds only if all of the steps in the computation succeed:
instance Monad Maybe where
return = Just
x >>= f = maybe Nothing f x
We also figured out how to bolt the behaviour of the Reader
monad on top of
any existing monad m
. The key was to thread the context not through a sequence
of applications of pure functions but through a sequence of actions in the
underlying monad m
. We do the same to equip an existing monad with the
abortion logic of Maybe
:
instance Monad m => Monad (MaybeT m) where
return x = MaybeT $ return $ Just x
x >>= f = MaybeT $ runMaybeT x >>= maybe (return Nothing) (runMaybeT . f)
return x
cannot fail, but the result of the computation wrapped by MaybeT
should be of type m (Maybe a)
. Thus, we wrap x
in Just
and then return it
using the return
method of our monad m
.
x >>= f
runs x
and binds its return value to
maybe (return Nothing) (runMaybeT . f)
. If x
returns Just y
, then we apply
runMaybeT . f
to y
, that is, we first apply f
to y
to obtain a value of
type MaybeT m b
, and then we run this computation using runMaybeT
, which
gives us a value of the desired type m (Maybe b)
. If x
returns Nothing
,
the next step in the computation also returns Nothing
. The result of the whole
computation is Nothing
, only wrapped in our monad m
as required. More
importantly, f
is never run. This is really important here. If the logic of
x >>= f
is that the computation should abort as soon as x
fails, then we
don't want to see any of the effects that f
may have; we really need to abort
the computation.