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
-
Even that's not quite the truth. As we will see at the end of this chapter,
ask
andasks
have the typesask :: 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 onlyReader
orReaderT
. Just as we pretended before thatask
andasks
were functions only for theReader
monad, let's pretend for now that they are functions only for theReaderT
monad transformer. We'll introduce the real definitions once we talk about theMonadReader
class.The same comments apply to
get
,gets
,put
, andtell
. We pretended before that these were functions for theState
andWriter
monads. As we'll see they also work for theStateT
andWriterT
monad transformers. But they're even more general. We'll discuss theMonadState
andMonadWriter
classes, which make sure that these functions are available in any monad that is an instance of these classes. ↩