在这种情况下,为什么在与嵌套的StateT monadT交互时不需要使用'lift'?

chi*_*ro2 10 monads haskell state-monad monad-transformers

假设我有一个monadT:

type Wrap a = ReaderT Env ( StateT Int ( StateT Int Identity ) ) a
Run Code Online (Sandbox Code Playgroud)

这里要注意的重要一点是,一个StateT正在包装另一个,并且两者都包含在第三个MonadT内,即ReaderT.

以及相应的runWrap函数以方便:

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
Run Code Online (Sandbox Code Playgroud)

一个通用的tock状态monad:

tock :: (Num s, MonadState s m) => m ()
tock = do modify (+1)
Run Code Online (Sandbox Code Playgroud)

我现在创建一个包裹monadT,其中我使用tock:

aWrap :: Wrap ( Int, Int )
aWrap = do
    lift tock
    lift . lift $ tock
    x    <- get
    y    <- lift . lift $ get
    return ( x, y )
Run Code Online (Sandbox Code Playgroud)

并运行它:

env = Map.fromList [('x', 1)]
runWrap env 1 200 aWrap
// answer: (201,2)
Run Code Online (Sandbox Code Playgroud)

根据lift我对如何与MonadT的嵌套层进行交互的理解,这里的使用对我有意义.

但是,这也有效,并给我相同的答案(201,2):

aWrap :: Wrap ( Int, Int )
aWrap = do
    tock
    lift . lift $ tock
    x    <- get
    y    <- lift . lift $ get
    return ( x, y )
Run Code Online (Sandbox Code Playgroud)

我想通过调用tockw/o lift,它看起来好像tock是应用于外部MonadT,即ReaderT,这没有任何意义.但为什么这有效呢?

PS请忽略Env这里的存在,它与问题无关,只是我正在使用的外部MonadT的选择.

dan*_*iaz 9

你很可能在MonadState没有意识到的情况下使用类型类.这个类型类在mtl包中定义(也在包中monads-fd).

MonadState允许你State在许多基于monad的堆栈中直接使用monad 的方法,而无需显式提升State.

看看线的以下两行:

Monad m => MonadState s (StateT s m)
MonadState s m => MonadState s (ReaderT r m)
Run Code Online (Sandbox Code Playgroud)

第一个说任何StateT都是MonadState(我们应该期待的!)的实例.第二个说任何ReaderT基地monad是MonadState一个实例,也是一个实例MonadState.这恰好是你的情况.

纵观源代码MonadState,我们发现:

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))
Run Code Online (Sandbox Code Playgroud)

如你所见,类型类的内部机器负责提升.

还有其他类型类提供类似的功能,比如MonadReader,MonadWriterMonadRWS.