假设我有一个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的选择。
答案 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))
如你所见,类型类的内部机器负责提升。
还有其他提供类似功能的类型类,例如MonadReader
,MonadWriter
和MonadRWS
。