robb.re

The EitherT Monad Transformer

Posted on August 6, 2015

This post will undoubtedly change as I further refine my understanding. This first pass is mainly so that I do not forget what I have learnt so far.

Let’s begin as is customary with a ridiculous example

What the above code does is report on the existence (or lack thereof) of each of the three files in turn (cabalFile, licenseFile, stackFile). As soon as a file is encountered that no longer exists then no further checks are performed. Note that the final message is always printed.

The chain of dependent case statements causes the ugly cascading indentation. Luckily we can remove this with eitherT.

How Does EitherT Help?

Well, reading from the eitherT docs

EitherT is a version of ErrorT that does not require a spurious Error instance for the Left case.

ErrorT is actually on the path to deprecation and ExceptT should be preferred but the docs of both indicate that they have the same basic functionality:

This monad transformer extends a monad with the ability to throw exceptions.

A sequence of actions terminates normally, producing a value, only if none of the actions in the sequence throws an exception. If one throws an exception, the rest of the sequence is skipped and the composite action exits with that exception.

so this allows us to exit from a code block early on the first failure. Our modified version now looks like this:

The first time we encounter a Left we return left err and that terminates the processing of the entire do block. If no Lefts are encountered we get to the end of this do block and simply return right "fin.".

Note that we don’t explicitly create an instance of EitherT ourselves and instead make use of runEitherT.

What is Happening Here?

A little bit about (my understanding of) what is going on with the runExceptT function.

The first thing that our original fileChecking function does is call file cabalFile to determine if that file exists. In ghci we can see the type of this action

This makes sense, the interesting part for us is the Either that wraps up the failure/success paths but since checking the file exists is an IO action we get our result wrapped in the IO type. So far so good.

Now if we look at the type of lift (imported from Control.Monad.Trans.Class) we see

it takes a monad m a and “transforms” it by augmenting it with the transformer monad t. In our case we can see the outcome of lifting our call to file cabalFile

Looking at this types shows that we’re now just missing the transforming monad so lets now look at the type of EitherT

EitherT creates a transformed Either with e (the failure case) on the left as expected and m a on the right. Remember that a is the success case and m is the monad that we’re augmenting (which in this case is IO). The effect of this is that our original action

now becomes

notice that we didn’t explicitly create EitherT ourselves though; lets look at the type of runEitherT to see what it does.

so runEitherT is the reverse of EitherT and reverts an EitherT to its original type which means that action we get back is the IO (Either String String) we started with except now we’ve performed all of our error checking.