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

时间:2013-03-25 22:15:42

标签: haskell monads monad-transformers state-monad

假设我有一个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状态monad:

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

我现在创建一个包裹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)

在我对如何与MonadT的嵌套层进行交互的理解方面,使用lift对我来说很有意义。

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

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

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

P.S。请忽略此处Env的存在,它与问题无关,只是我正在使用的外部MonadT的选择。

1 个答案:

答案 0 :(得分:9)

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

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

请查看haddocks中的以下两行:

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

第一个说任何StateT都是MonadState的一个实例(正如我们所期望的那样!)。第二个说明基础monad是ReaderT实例的任何MonadState也是MonadState的实例。这恰好是你的情况。

查看MonadState的{​​{3}},我们发现:

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))

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

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