Question

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.

Was it helpful?

Solution

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top