Skip to content

ReaderT

Remember the definition of the Reader monad we used before:

newtype Reader r a = Reader { runReader :: r -> a }

It wraps a function that computes a pure value of type a from the given context of type r. ReaderT does the same for an underlying monad m:

newtype ReaderT r m a = ReaderT { runReaderT :: r -> m a }

The only difference is that the wrapped function isn't pure; it decorates its return type with the monad m. So ReaderT r IO a is a function that maps a context of type r to an IO action with return type a. The right way to think this is that ReaderT r IO a is an action that can do both, depend on a context r and perform I/O. It's the IO monad equipped with some read-only context of type r. In the ReaderT r IO a monad, we can perform I/O using all the standard I/O functions, only we need to lift them using lift. So putStrLn "Hello world!" becomes lift $ putStrLn "Hello world!". We can also read the context using ask and asks. This requires no lifting because the outermost monad is ReaderT.

This means that ask and asks aren't actually defined as

ask :: Reader r r
asks = Reader id

asks :: (r -> a) -> Reader r a
asks f = Reader f

Instead, they are defined to work with the ReaderT monad transformer and any possible underlying monad m:1

ask :: Monad m => ReaderT r m r
ask = ReaderT return

asks :: Monad m => (r -> a) -> ReaderT r m a
asks f = ReaderT (return . f)

The function wrapped by ReaderT r m r should have the type r -> m r. return has this type, and it behaves correctly because ask is not supposed to do anything except return the context, which is what return does. Similarly, asks f is supposed to extract a portion of the context using the function f. Its type is Reader T r m a, that is, it is a wrapper for a function of type r -> m a. f has type r -> a. return has type a -> m a. By composing these two functions, we obtain the function return . f :: r -> m a, again exactly the type we need.

So how do we make ReaderT a monad? Remember our original implementation of Reader. The idea was to thread the context r through a computation composed of pure functions. Here is how we did this:

instance Monad (Reader r) where
    return x = Reader (const x)
    x >>= f  = Reader $ \r -> let y = runReader x r
                              in  runReader (f y) r

The implementation of ReaderT looks essentially the same, only runReaderT x is not a pure value now. If x has the type ReaderT r m a, then runReaderT x r has the type m a.

Let's recall the idea of what return x is expected to do: It should take a pure value x :: a and turn it into a value of type m a that is x equipped with the decoration of our monad m. Now we have two decorations: the one provided by the monad m and the one provided by the ReaderT monad transformer. Our implementation of return has to equip its argument x with both decorations. To this end, we use the return function of m to equip x with m's decoration. The decoration provided by ReaderT r is the ability to depend on a context of type r, only return completely ignores this context. Thus, we have

return x = ReaderT $ \r -> return x

or, more succinctly,

return x = ReaderT $ const (return x)

The expression x >>= f should run x in the context r and then apply f to the result, also in the context r. However, runReaderT x r has the type m a, and f has the type a -> ReaderT r m b, which essentially the same as (formally, is isomorphic to) the type a -> r -> m b. Thus, we cannot simply apply f to runReaderT x r. We need to thread the decoration provided by the monad m through the computation, by using m bind operator:

x >>= f = ReaderT $ \r -> do
    y <- runReaderT x r
    runReaderT (f y) r

This gives the following Monad instance for ReaderT r m:

instance Monad m => Monad (ReaderT r m) where
    return x = ReaderT $ const (return x)
    x >>= f  = ReaderT $ \r -> do
        y <- runReaderT x r
        runReaderT (f y) r

  1. Even that's not quite the truth. As we will see at the end of this chapter, ask and asks have the types

    ask  :: MonadReader r m => m r
    asks :: MonadReader r m => (r -> a) -> m a
    

    They work for any monad that is an instance of the MonadReader class, not only Reader or ReaderT. Just as we pretended before that ask and asks were functions only for the Reader monad, let's pretend for now that they are functions only for the ReaderT monad transformer. We'll introduce the real definitions once we talk about the MonadReader class.

    The same comments apply to get, gets, put, and tell. We pretended before that these were functions for the State and Writer monads. As we'll see they also work for the StateT and WriterT monad transformers. But they're even more general. We'll discuss the MonadState and MonadWriter classes, which make sure that these functions are available in any monad that is an instance of these classes.