This isn’t a monad tutorial, although I might do one some day. I’m going to assume a basic understanding of monads and some common functions from Control.Monad for this article.
Monads are rather useful. We can define all softs of computations with them: IO, non-determinism, random, stateful, etc. But what if we want to use multiple monads in a single computation?
Enter monad transformers, a class and framework for defining monads that can contain other monads.
Say you want to define a computation that consumes random numbers and makes use of some read-only state. We can use the Reader and Random (Rand) monads. We could define a Rand computation inside Reader, but that would require a lot of carting around of StdGen values; in and out of the computation. It could get messy, but what if there was a better way? Both Reader and Rand include transformer versions (ReaderT and RandT) but we will only use the transformer version of Reader.
The ReaderT type constructor takes a state type (r), a monad (m) and a return type (a):
ReaderT r m a
The Rand type constructor takes a RandomGen instance (g) – usually StdGen from System.Random – and a return type (a):
Rand g a
A combination of one or more monad transformers and a base monad is commonly referred to as a “monad stack”. In our case, our monad stack’s type is as follows:
ReaderT Int (Rand StdGen) String
Here, ReaderT is transforming our base monad (Rand StdGen). Computations inside this monad are in ReaderT but we can use lift from the MonadTrans type class to “lift” Rand functions into the ReaderT monad.
To evaluate or “run” the ReaderT monad transformer you must use the runReaderT function:
runReaderT :: r -> m a
All monad transformers provide an equivelant function following the naming convention runMonadNameT.
Running runReaderT on our computation yields a Rand StdGen String value. Essentially “popping” ReaderT off our monad stack. The final String must be obtained by evaluating the Rand monad with evalRand.
In the following example program, we use the same monad stack as before, be we make use of replicateM from Control.Monad to generate multiple String results to demonstrate the random generation.
So, monad transformers are a powerful mechanism to enable multi-monadic computations. Now that I understand them, they’re going to be very useful in my final year engineering project at university.