MTL
Ordinary monad transformers
Using ordinary monad transformers, we would have to construct a transformer stack like the following example:
type Environment = [(String, Int)]
type Counter = Int
newtype M a = M (ReaderT Environment (StateT Counter IO) a)
deriving (Functor, Applicative, Monad)
-- This inverts into something like IO (State Counter (Reader Environment a))
In order to access the environment, we can use the ask
operation from Control.Monad.Trans.Reader
, but we have to wrap this up in the M
newtype
.
env :: M Environment
env = M ask
If we want to retrieve the current state in the computation, we can use the get
operation from Control.Monad.Trans.State
, but we also have to lift
that into the ReaderT
monad that is wrapping the StateT
monad.
currentState :: M State
currentState = M (lift get)
• Example definitions
data Maybe a = Just a | Nothing
newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }
data List a = Cons a (List a) | Nil
newtype ListT m a = ListT { runListT :: m [a] }
data Either e a = Left e | Right a
newtype ExceptT e m a = ExceptT { runExceptT :: m (Either e a) }
newtype State s a = State { runState :: s -> (a, s) }
newtype StateT s m a = StateT { runStateT :: s -> m (a, s) }
newtype Writer w a = Writer { runWriter :: (a, w) }
newtype WriterT w m a = WriterT { runWriterT :: m (a, w) }
newtype Reader r a = Reader { runReader :: r -> a }
newtype ReaderT r m a = ReaderT { runReaderT :: r -> m a }
MTL
The mtl
library defines various type classes that abstract over the operations provided by each monad transformer.
For ReaderT
:
newtype ReaderT r m a = ReaderT { runReaderT :: r -> m a }
We have the MonadReader
type class which contains the ask
operation:
class MonadReader r m | m -> r where
ask :: m r
A subset of the instances for MonadReader
for example, are:
instance Monad m => MonadReader r (ReaderT r m) where
ask = Control.Monad.Trans.ReaderT.ask
instance (MonadReader r m) => MonadReader r (StateT s) where
ask = lift ask
This can be read as:
- A base case, if the outermost transformer is
ReaderT
, in which case nolift
ing has to be performed. - An inductive case, stating that if we know there is a
MonadReader
instance somewhere within the stack (i.e. there exists aReaderT
in the stack), then the outer monad transformer (in this caseStateT
) is also an instance ofMonadReader
by simply passing those operations through to the underlying instance via one application oflift
.
These type class instances mean that lifting becomes automatic entirely at the usage of the respective operations. Programs hence become more generic and easier to reason about, as well as it being easier to use the operations.
The previous function for getting the environment from an ordinary monad transformer stack (which had type M Environment
) can now be generalised to the following, which states the env
is reusable in any computation that has access to Environment
.
env :: (MonadReader Environment m) => m Environment
env = ask
• Example definitions
The MonadWriter
class:
class (Monoid w, Monad m) => MonadWriter w m | m -> w where
writer :: (a, w) -> m a
writer ~(a, w) = do
tell w
return a
tell :: w -> m ()
tell w = writer ((), w)
The MonadState
class:
class Monad m => MonadState s m | m -> s where
get :: m s
get = state (\s -> (s, s))
put :: s -> m ()
put s = state (\_ -> ((), s))
The MonadError
class:
class (Monad m) => MonadError e m | m -> e where
throwError :: e -> m a