Home > Back-end >  Confusing ReaderT definition
Confusing ReaderT definition

Time:06-18

As an exercise I've been reimplementing some common monads and their corresponding transformers; here are some of the types I've defined:

newtype Writer  w   a = Writer  { runWriter  :: (w, a) }
newtype WriterT w m a = WriterT { runWriterT :: m (Writer w a) }

newtype Maybe    a = Just a | Nothing
newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }

As I get it a transformer "wraps" a monad in an outer monad m; following this intuition I tried defining the ReaderT transformer in a similar way as:

newtype Reader  r   a = Reader  { runReader  :: r -> a }
newtype ReaderT r m a = ReaderT { runReaderT :: m (Reader r a) }

However, I got stuck implementing its monad instance. By looking at the library definition of ReaderT I see that it is defined as

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

Aside from the fact that it does not use Reader (that can be defined in terms of ReaderT), why is it defined as r -> m a and not m (r -> a)? I thought it should wrap the inner Reader (i.e. r -> a) with the outer monad m obtaining m (r -> a) and not r -> m a

CodePudding user response:

m (r -> a) means that you:

  1. First perform some action in the monad, and then,

  2. After the action is complete, perform a pure calculation on the reader value.

But you need to be able to use ask:

ask :: Monad m => ReaderT r m r

With ask, you can:

  1. First read the value,

  2. Then perform some monadic action, acting on the value.

That is why r -> m a makes sense and m (r -> a) does not make sense.

If it helps, think of this as “symmetrical” with the writer monad. With the writer monad, the monad is on the outside, and the result is on the inside. With the reader monad, the input is on the outside, and the monad is on the inside. This kind of symmetry shows up a lot.

  • Related