Monads allow us to build computations that have effects. However, in "real code", we often need to be able to use several effects at once. This is what monad transformers allow us to do.
A monad transformer modifies the behavior of the underlying monad. It modifies the behavior in that it adds teh effect of the base monad to the inner monad.
Some examples:
- The
StateT
monad transformer adds mutable state to the underlying monad. - The
MaybeT
monad transformer adds failure to the underlying monad. - The
EitherT
/ExceptT
monad transformer adds error messages to the underlying monad.
Monad transformers allow us to build a monadic stack. In the example below,
WriterT
is a monad transformer and MonadicStack
is our monadic stack.
Furthermore, we say that MonadicStack
is a stack with WriterT
stacked on top
of IO
.
newtype WriterT w m a = WriterT { runWriterT :: m (a, w) }
type MonadicStack = WriterT [String] IO
Finally, another very important aspect of monad transformers is that partial application of the monad transformer type constructor is itself monad. This property means that monadic stacks can be used as an underlying monad in a new stack:
newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }
type StackOnStack = MaybeT MonadicStack
Our StackOnStack
type synonym is a monadic stack where the underlying monad is
another monadic stack. The StackOnStack
type synonym is equivalent to:
-- StackOnStack ~ MaybeT (WriterT [String] IO)
StackOnStack
is a monad that has the effects of both a Writer
monad and a
Maybe
monad and an IO
monad; we get logging, failure, and IO effects.
At the value leve, we know that when we "run" the StackOnStack
monad, we are
going to get an IO (Maybe a, [String])
- an IO action that returns a tuple, an
that tuple contains an a
(maybe) and a list of strings. How do we know this?
Through the substitution model:
First, we start of with type StackOnStack = MaybeT MonadicStack
.
Second, we substitute MaybeT
for its definition:
MaybeT MonadicStack = MaybeT { runMaybeT :: MonadicStack (Maybe a)}
Third, we substitue MonadicStack
for its definition:
MaybeT { runMaybeT :: MonadicStack (Maybe a ) } = MaybeT { runMaybeT :: WriterT [String] IO (Maybe a) }
Next, we substitue WriterT
for its definition:
MaybeT { runMaybeT :: WriterT [String] IO (Maybe a) } = MaybeT { runMaybeT :: IO (Maybe a, [String]) }