Building Monad Transformers - Part 1

In this article we’ll focus on building our own monad transformers.
We’ll start out with an example code and improve it by building a simple
wrapper over IO (Maybe a).

The following example is really simple, but I’m sure you can imagine
doing something similar in your own application. The findById method
is there just to simulate a database query that might not find a result.

While there’s nothing bad about case statements with pattern matching
I’m sure we can all agree that this approach can easily blow out of
proportions.

One solution that won’t work all the time might be to fetch both of the users
at the same time, which would allow us to make use of the Maybe monad. If our
findById function didn’t do any side effects, we could’ve written this.

Because Maybe is implemented in a way that it stops evaluating when it hits
on Nothing we get the behavior we intended without pattern matching. If one of
our findById fails to return a user, the whole function will return a
Nothing.

Unfortunately the act of finding a user needs to reach out to the real world,
which forces the IO monad upon us, making this approach impossible. We
somehow need to be able to teach IO the notion of failure.

Wrapping IO in MaybeIO

Let’s introduce a new monad which will simply wrap our IO computations into a
Maybe.

dataMaybeIOa=MaybeIO{runMaybeIO::IO(Maybea)}

The next step is to make MaybeIO into a Monad, which will allow us to use
it inside a do block, but first things first. The next version of GHC (7.10)
will require every Monad to also be an Applicative, which also means that
every Monad must be a Functor. We’ll follow this an start out with a
Functor instance.

instanceFunctorMaybeIOwherefmapfm=undefined

We’ll use type holes to hint us in while implementing these instances. First
let’s recap the type of fmap, which is (a -> b) -> f a -> f b, which means
we have a function f :: a -> b and a functor value m :: f a, or
specifically m :: MaybeIO a.

Before we can do anything to the m we need to unwrap MaybeIO to get to the
insides. We’ll use pattern matching to do that since it’s more concise
than using runMaybeIO.

instanceFunctorMaybeIOwherefmapf(MaybeIOm)=undefined

We only have two things available to us, the function f :: a -> b
which only works on the type a, and the fact that both Maybe and
IO are also Functor instances, which means we can use fmap to
reach deep into the Maybe (IO a) to apply our function f to get the
result.

Here comes a little trick, since fmap can also be thought of as (a -> b) ->
(f a -> f b). If we compose fmap with fmap, it gives us exactly what we
need, a way to reach two functors deep to apply a function.

λ>:tfmap.fmap::(Functorf,Functorg)=>(a->b)->f(ga)->f(gb)

Substituting our types we get the following.

fmap.fmap::(a->b)->(IO(Maybea)->IO(Maybeb))

We are not there quite yet, let’s see what happens if we use this
approach to implement the Functor instance.

Now remember how in the beginning we said we’ll be wrapping the IO (Maybe a)
into a MaybeIO? We can do that using the constructor of MaybeIO!

instanceFunctorMaybeIOwherefmapf(MaybeIOm)=MaybeIO$(fmap.fmap)fm

There you go, a Functor instance for MaybeIO.

Applicative instance for MaybeIO

The next step is to implement an Applicative instance for our MaybeIO
wrapper. Here’s how the Applicative class looks in case you forgot.

classApplicativemwherepure::a->ma(<*>)::m(a->b)->ma->mb

In terms of our MaybeIO the types would look as following.

pure::a->MaybeIOa(<*>)::MaybeIO(a->b)->MaybeIOa->MaybeIOb

Implementing pure is simple, we just need to wrap a given value into a
minimal context. Since both Maybe and IO are an instance of Applicative,
we can use their pure much as we used fmap when implementing the Functor
instance (don’t forget to import Control.Applicative.)

instanceApplicativeMaybeIOwherepure=MaybeIO.pure.pure

We could’ve also written this more explicitly using Just instead of pure
for wrapping the value in a Maybe.

instanceApplicativeMaybeIOwherepure=MaybeIO.pure.Just

But moving on, now comes the hard part, implementing <*>. This is probably
the hardest part of the whole article, so don’t worry if it seems a bit
complicated. First we need to pattern match to get rid of the MaybeIO
wrapper, and then we also need to wrap the value on the right hand side in the
last step.

The type hole tells us that we need to somehow get to a IO (Maybe b) with the
given IO (Maybe (a -> b)) and IO (Maybe a). This seems like a typical
reach into a box/context and apply a function kind of problem, and it is, but
we do need to do something which isn’t so apparent at first.

Both Maybe and IO are an instance of Applicative, which means we need to
somehow use <*> to apply the boxed function to the boxed value (pardon me for
saying boxed here, but it just seems like the right analogy here.)

The problem is that we can only use <*> to apply a function nested one level
deep, since the type is m (a -> b) -> m a -> m b. Knowing that <*> is a two
argument function, meaning we can’t use simple ., we need to look into the
documentation for Applicative and find the function liftA2, works just like
fmap on functors, but for two argument functions.

λ>:tliftA2::Applicativef=>(a->b->c)->fa->fb->fc

If we combine these two together we do get exactly what we need, a function
which takes two arguments, where first one is a function nested in two
applicatives, and a value, and applies the function to that value.

The problem here is that m >>= \x -> ... must have a return value of
IO, but we’re trying to return MaybeIO. This is why we need to
unwrap the result of f val and then wrap it again after doing >>=,
as we did in the previous example.

Using MaybeIO to cleanup our initial example

We manage to build ourselves a monad which combines the effects of IO and
Maybe together, which means we can use it to represent IO computations
which can fail. This is perfect for our initial example which uses findById ::
Int -> IO (Maybe User).

Since the type of our computation is MaybeIO we need to wrap the findById
function to make use of the monad instance for MaybeIO.

Now let’s go ahead and test this in GHCi to make sure we didn’t break anything.

λ>smartFindUsers11Just(User,User)

Our new version works exactly the same as the old one, but without the
necessary error handling boilerplate. Much like monads allow you to capture
control flow patterns, you can use monad transformers to add additional control
flow to your existing monads without sacrificing readability of your code.

The next step is to make our MaybeIO into an actual transformer by swapping
IO for any Monad.

Generalizing MaybeIO to MaybeT

The real monad transformers you’ll encounter in the world of Haskell are a bit
more generic than the one we just implemented. Instead of hard-coding the IO
monad we’ll pass it in as a type parameter, resulting in the following
definition of MaybeT.

newtypeMaybeTma=MaybeT{runMaybeT::m(Maybea)}

There aren’t any significant changes, we just introduced a new type parameter
which will be the monad we’re wrapping. Since everything else remains almost
exactly the same, I’ll just show the Monad implementation here.

If you’re interested in learning more about the Identity monad and how it can
be used in some more advanced settings, take a look at my Introduction to
Lenses
article
where it’s explained step by step in great detail.

This concludes the first article in the series on Monad Transformers. Next time
we’ll take a look at how we can stack one transformer onto another and
introduce the MonadTrans and MonadIO type classes.