Skip to content

Derived Instances

Everything we have done so far could have been done without defining the MonadReader, MonadWriter, and MonadState type classes. In fact, we did do all of this, at least for the key functions we care about: get, put, ask, tell. Now let's reap the fruits of our labour. What we want to achieve is that the monad produced by wrapping some monad m in ReaderT, WriterT or MaybeT is still a state monad if the underlying monad m is a state monad. Formally, if m is an instance of MonadState, then so is ReaderT r m, WriterT w m, and MaybeT m. This should work recursively: MonadReaderT r (MonadWriterT w (MaybeT (State s))) should also be a state monad. And we want the same for MonadReader and MonadWriter. Wrapping a reader monad or writer monad in some state transformer, we want the resulting monad to be an instance of MonadReader or MonadWriter.

In principle, the following should work:

instance (MonadTrans t, MonadReader r m) => MonadReader r (t m) where
    ask    = lift ask
    local  = lift . local
    reader = lift . reader

instance (MonadTrans t, MonadWriter w m) => MonadWriter w (t m) where
    tell   = lift . tell
    listen = lift . listen
    pass   = lift . pass
    writer = lift . writer

instance (MonadTrans t, MonadState s m) => MonadState s (t m) where
    get   = lift get
    put   = lift . put
    state = lift . state

We obtain MonadReader, MonadWriter, and MonadState instances by simply lifting the methods of the underlying monad into the enclosing monad.

Here's why this doesn't quite work: Consider the monad ReaderT Int (Reader Char). That's a reader monad stacked on top of another Reader monad, but that's fine. There are scenarios where this even makes sense. We have a MonadReader Int (ReaderT Int m) instance for any monad m, including m = Reader Char. Since ReaderT Int is a monad transformer, the above definition also gives us the instance MonadReader Char (ReaderT Int (Reader Char)). And now we have a problem: If we use ask in our ReaderT Int (Reader Char) monad, the compiler cannot tell whether we want to access the context of the Reader Char monad, of type Char, or the context provided by the ReaderT Int wrapper, of type Int. Even if both types were the same, say ReaderT Int (Reader Int), the type of ask would be unambiguous, but the compiler still wouldn't know which Int we want to read. And that's exactly where the functional dependency of the MonadReader class comes in. It says that there can be at most one instance MonadReader r m for any monad m, but here we have two instances: the instance for ReaderT Int m for any monad, and the instance for ReaderT Char m by virtue of ReaderT being a monad transformer and Reader being an instance of MonadReader.

Unfortunately, the only way out seems to be to define custom instances for each monad transformer:

instance MonadReader r m => MonadReader r (WriterT w m) where
    ask    = lift ask
    local  = lift . local
    reader = lift . reader

instance MonadReader r m => MonadReader r (StateT s m) where
    ask    = lift ask
    local  = lift . local
    reader = lift . reader

instance MonadReader r m => MonadReader r (MaybeT s m) where
    ask    = lift ask
    local  = lift . local
    reader = lift . reader

instance MonadWriter w m => MonadWriter w (ReaderT r m) where
    tell   = lift . tell
    listen = lift . listen
    pass   = lift . pass
    writer = lift . writer

instance MonadWriter w m => MonadWriter w (StateT s m) where
    tell   = lift . tell
    listen = lift . listen
    pass   = lift . pass
    writer = lift . writer

instance MonadWriter w m => MonadWriter w (MaybeT m) where
    tell   = lift . tell
    listen = lift . listen
    pass   = lift . pass
    writer = lift . writer

instance MonadState s m => MonadState s (ReaderT r m) where
    get   = lift get
    put   = lift . put
    state = lift . state

instance MonadState s m => MonadState s (WriterT w m) where
    get   = lift get
    put   = lift . put
    state = lift . state

instance MonadState s m => MonadState s (MaybeT m) where
    get   = lift get
    put   = lift . put
    state = lift . state

The unfortunate downside is that we don't automatically get MonadState, MonadWriter, and MonadReader instances for any monad obtained by wrapping an arbitrary monad transformer around a state, writer or reader monad. We have to define such instances manually for every new monad transformer we implement.