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.