| Prev: Standard monad transformers | TOC: Contents | Next: More examples with monad transformers |
In this section, we will take a detailed look at the implementation of one
of the more interesting transformers in the standard library, StateT.
Studying this transformer will build insight into the transformer mechanism
that you can call upon when using monad transformers in your code.
You might want to review the section on the State monad
before continuing.
Just as the State monad was built upon the definition
newtype State s a = State { runState :: (s -> (a,s)) }
the StateT transformer is built upon the definition
newtype StateT s m a = StateT { runStateT :: (s -> m (a,s)) }
State s is an instance of both the Monad
class and the MonadState s class, so
StateT s m should also be members of the
Monad and MonadState s classes.
Furthermore, if m is an instance of MonadPlus,
StateT s m should also be a member of
MonadPlus.
To define StateT s m as a Monad instance:
newtype StateT s m a = StateT { runStateT :: (s -> m (a,s)) }
instance (Monad m) => Monad (StateT s m) where
return a = StateT $ \s -> return (a,s)
(StateT x) >>= f = StateT $ \s -> do (v,s') <- x s -- get new value, state
(StateT x') <- return $ f v -- apply bound function to get new state transformation fn
x' s' -- apply the state transformation fn to the new state
|
State s.
Our definition of return makes use of the return
function of the inner monad, and the binding operator uses a do-block
to perform a computation in the inner monad.
We also want to declare all combined monads that use the StateT
transformer to be instaces of the MonadState class, so
we will have to give definitions for get and put:
instance (Monad m) => MonadState s (StateT s m) where
get = StateT $ \s -> return (s,s)
put s = StateT $ \_ -> return ((),s)
|
Finally, we want to declare all combined monads in which StateT is used
with an instance of MonadPlus to be instances of MonadPlus:
instance (MonadPlus m) => MonadPlus (StateT s m) where
mzero = StateT $ \s -> mzero
(StateT x1) `mplus` (StateT x2) = StateT $ \s -> (x1 s) `mplus` (x2 s)
|
The final step to make our monad transformer fully integrated with Haskell's
monad classes is to make StateT s an instance of the
MonadTrans class by providing a lift function:
instance MonadTrans (StateT s) where
lift c = StateT $ \s -> c >>= (\x -> return (x,s))
|
lift function creates a StateT state transformation
function that binds the computation in the inner monad to a function that
packages the result with the input state. The result is that a function
that returns a list (i.e., a computation in the List monad) can be lifted
into StateT s [], where it becomes a function that returns a
StateT (s -> [(a,s)]). That is, the lifted computation
produces multiple (value,state) pairs from its input state.
The effect of this is to "fork" the computation in StateT, creating a different
branch of the computation for each value in the list returned by the lifted function.
Of course, applying StateT to a different monad will produce different
semantics for the lift function.
We have examined the implementation of one monad transformer above, and it was stated earlier that there was no magic formula to produce transformer versions of monads. Each transformer's implementation will depend on the nature of the computational effects it is adding to the inner monad.
Despite this, there is some theoretical foundation to the theory of monad
transformers. Certain transformers can be grouped according to how they
use the inner monad, and the transformers within each group can be derived
using monadic functions and functors. Functors, roughly, are
types which support a mapping operation
fmap :: (a->b) -> f a -> f b.
To learn more about it, check out Mark Jones' influential
paper that inspired
the Haskell monad template library.
| Prev: Standard monad transformers | TOC: Contents | Next: More examples with monad transformers |