Probabilistic Effects. λθ

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 no lifting 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 a ReaderT in the stack), then the outer monad transformer (in this case StateT) is also an instance of MonadReader by simply passing those operations through to the underlying instance via one application of lift.

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
Last updated on 13 Nov 2020
Published on 13 Nov 2020