Let's say I have a monadT:
type Wrap a = ReaderT Env ( StateT Int ( StateT Int Identity ) ) a
The important thing to note here is that one StateT is wrapping another, and both are wrapped inside a third MonadT, namely ReaderT.
and the corresponding runWrap function for convenience:
type Env = Map.Map Char Integer
runWrap :: Env -> Int -> Int -> Wrap a -> a
runWrap env st1 st2 m = runIdentity $ evalStateT ( evalStateT ( runReaderT m env ) st2 ) st1
And a generic tock state monad:
tock :: (Num s, MonadState s m) => m ()
tock = do modify (+1)
I now create a wrap monadT where inside I use tock:
aWrap :: Wrap ( Int, Int )
aWrap = do
lift tock
lift . lift $ tock
x <- get
y <- lift . lift $ get
return ( x, y )
And run it:
env = Map.fromList [('x', 1)]
runWrap env 1 200 aWrap
// answer: (201,2)
The use of lift
here makes sense to me in terms of my understanding of how to interact w/ nested layers of MonadT.
However, this also works and give me the same answer: (201,2)
:
aWrap :: Wrap ( Int, Int )
aWrap = do
tock
lift . lift $ tock
x <- get
y <- lift . lift $ get
return ( x, y )
I would think by calling tock
w/o lift
, it reads as if tock
is applied to the outer MonadT, namely ReaderT, which makes no sense. But why does this work?
P.S. Please ignore the presence of Env
here, it has nothing to do w/ the question, just the choice of the outer MonadT I'm using.
Your are likely using the MonadState
typeclass without being aware of it. This typeclass is defined in the mtl
package (and in monads-fd
, too).
MonadState
allows you to use the methods of the State
monad, directly and without explicit lifting, in many monad stacks based on State
.
Look at the following two lines in the haddocks:
Monad m => MonadState s (StateT s m)
MonadState s m => MonadState s (ReaderT r m)
The first one says that any StateT
is an instance of MonadState
(as we should expect!). The second one says that any ReaderT
whose base monad is an instace of MonadState
, is also an instance of MonadState
. Which happens to be your case.
Looking at the source code for MonadState
, we find:
instance MonadState s m => MonadState s (ReaderT r m) where
get = lift get
put = lift . put
state = lift . state
modify :: MonadState s m => (s -> s) -> m ()
modify f = state (\s -> ((), f s))
As you see, the inner machinery of the typeclass takes care of the lifting.
There are other typeclasses which offer similar functionality, like MonadReader
, MonadWriter
and MonadRWS
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With