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的选择.
你很可能在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,MonadWriter和MonadRWS.