10

假设我有一个 monadT:

type Wrap a = ReaderT Env ( StateT Int ( StateT Int Identity ) ) a

这里要注意的重要一点是,一个 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

还有一个通用的 tock 状态单子:

tock :: (Num s, MonadState s m) => m ()
tock = do modify (+1)

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

aWrap :: Wrap ( Int, Int )
aWrap = do
    lift tock
    lift . lift $ tock
    x    <- get
    y    <- lift . lift $ get
    return ( x, y )

并运行它:

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

lift就我对如何与 MonadT 的嵌套层进行交互的理解而言,这里的使用对我来说很有意义。

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

aWrap :: Wrap ( Int, Int )
aWrap = do
    tock
    lift . lift $ tock
    x    <- get
    y    <- lift . lift $ get
    return ( x, y )

我认为通过调用tockw/o lift,它看起来好像tock应用于外部 MonadT,即 ReaderT,这是没有意义的。但为什么这行得通?

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

4

1 回答 1

9

您可能在MonadState不知道的情况下使用类型类。这个类型类是在mtl包中定义的(monads-fd也在 中)。

MonadState允许您State在许多基于State.

查看haddocks中的以下两行:

Monad m => MonadState s (StateT s m)
MonadState s m => MonadState s (ReaderT r m)

第一个说 anyStateTMonadState(如我们所料!)的一个实例。第二个说任何ReaderT其基本单子是 的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))

如您所见,类型类的内部机制负责提升。

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

于 2013-03-25T22:46:11.530 回答