StateT 超过 Cont。为什么我的状态没有被重置?
StateT over Cont. Why is my state not being reset?
我正在玩 Cont
描述的 monad 技巧 here and in this SO question。
此函数可以让您 "jump back" 更早地进行计算,获取一个参数,以便您可以以不同的方式做事:
import Control.Monad.Cont
import Control.Monad.State.Strict
import Control.Monad.Writer.Strict
getCC' :: MonadCont m => a -> m (a,a -> m b)
getCC' x0 = callCC (\c -> let f x = c (x, f) in return (x0, f))
我在 Cont
:
上面有这些 monad 转换器的玩具示例
foo :: WriterT String (Cont String) ()
foo = do
(stop,loop) <- getCC' False
if stop
then do tell "bbb"
else do tell "aaa"
loop True
foo' :: StateT String (Cont String) ()
foo' = do
(stop,loop) <- getCC' False
if stop
then do modify $ \s -> s ++ "bbb"
else do modify $ \s -> s ++ "aaa"
loop True
在第一个示例中(如链接 SO 问题中的 explained),Cont
的效果 "priority" 超过 WriterT
的效果。当我们重置计算时,日志丢失:
*Main> print $ runCont (execWriterT foo) id
"bbb"
第二个例子做同样的事情,只是使用 StateT
而不是 WriterT
。但是,在这种情况下,日志会被保留!
*Main> print $ runCont (execStateT foo' "") id
"aaabbb"
这种差异的解释是什么?
(我觉得这不是一个完全令人满意的答案,但至少应该澄清一点。)
我相信这是因为 callCC
的解除。在 state monad 的情况下,在将兔子追到洞里之后,我们遇到了这个:
liftCallCC :: CallCC m (a, s) (b, s) -> CallCC (StateT s m) a b
Uniform lifting of a callCC operation to the new monad. This version
rolls back to the original state on entering the continuation.
liftCallCC' :: CallCC m (a, s) (b, s) -> CallCC (StateT s m) a b
In-situ lifting of a callCC operation to the new monad. This version
uses the current state on entering the continuation.
拍的是哪一张?一个保存状态:
instance MonadCont m => MonadCont (LazyState.StateT s m) where
callCC = LazyState.liftCallCC' callCC
instance MonadCont m => MonadCont (StrictState.StateT s m) where
callCC = StrictState.liftCallCC' callCC
writer monad 会怎样?
instance (Monoid w, MonadCont m) => MonadCont (LazyWriter.WriterT w m) where
callCC = LazyWriter.liftCallCC callCC
instance (Monoid w, MonadCont m) => MonadCont (StrictWriter.WriterT w m) where
callCC = StrictWriter.liftCallCC callCC
啊哈!没有'
!
liftCallCC :: Monoid w => CallCC m (a, w) (b, w) -> CallCC (WriterT w m) a b
Lift a callCC operation to the new monad.
在库中找不到状态保持变体。相反,上面的变体在那里定义为
liftCallCC callCC f = WriterT $
callCC $ \ c ->
runWriterT (f (\ a -> WriterT $ c (a, mempty)))
注意 mempty
。如果我们有一个 get
操作,我们可以将 "current state" 存储在那里,这样它就不会在这个过程中丢失,但是如果我们有那个,我们将不再在 writer monad 中,而是在状态一。
另请注意,以相反顺序堆叠 monad 可实现我们想要的效果。
bar :: ContT String (Writer String) ()
bar = do
(stop,loop) <- getCC' False
if stop
then do lift $tell "bbb"
else do lift $ tell "aaa"
loop True
-- > runWriter (runContT bar (const $ pure ""))
-- ("","aaabbb")
我正在玩 Cont
描述的 monad 技巧 here and in this SO question。
此函数可以让您 "jump back" 更早地进行计算,获取一个参数,以便您可以以不同的方式做事:
import Control.Monad.Cont
import Control.Monad.State.Strict
import Control.Monad.Writer.Strict
getCC' :: MonadCont m => a -> m (a,a -> m b)
getCC' x0 = callCC (\c -> let f x = c (x, f) in return (x0, f))
我在 Cont
:
foo :: WriterT String (Cont String) ()
foo = do
(stop,loop) <- getCC' False
if stop
then do tell "bbb"
else do tell "aaa"
loop True
foo' :: StateT String (Cont String) ()
foo' = do
(stop,loop) <- getCC' False
if stop
then do modify $ \s -> s ++ "bbb"
else do modify $ \s -> s ++ "aaa"
loop True
在第一个示例中(如链接 SO 问题中的 explained),Cont
的效果 "priority" 超过 WriterT
的效果。当我们重置计算时,日志丢失:
*Main> print $ runCont (execWriterT foo) id
"bbb"
第二个例子做同样的事情,只是使用 StateT
而不是 WriterT
。但是,在这种情况下,日志会被保留!
*Main> print $ runCont (execStateT foo' "") id
"aaabbb"
这种差异的解释是什么?
(我觉得这不是一个完全令人满意的答案,但至少应该澄清一点。)
我相信这是因为 callCC
的解除。在 state monad 的情况下,在将兔子追到洞里之后,我们遇到了这个:
liftCallCC :: CallCC m (a, s) (b, s) -> CallCC (StateT s m) a b
Uniform lifting of a callCC operation to the new monad. This version rolls back to the original state on entering the continuation.
liftCallCC' :: CallCC m (a, s) (b, s) -> CallCC (StateT s m) a b
In-situ lifting of a callCC operation to the new monad. This version uses the current state on entering the continuation.
拍的是哪一张?一个保存状态:
instance MonadCont m => MonadCont (LazyState.StateT s m) where
callCC = LazyState.liftCallCC' callCC
instance MonadCont m => MonadCont (StrictState.StateT s m) where
callCC = StrictState.liftCallCC' callCC
writer monad 会怎样?
instance (Monoid w, MonadCont m) => MonadCont (LazyWriter.WriterT w m) where
callCC = LazyWriter.liftCallCC callCC
instance (Monoid w, MonadCont m) => MonadCont (StrictWriter.WriterT w m) where
callCC = StrictWriter.liftCallCC callCC
啊哈!没有'
!
liftCallCC :: Monoid w => CallCC m (a, w) (b, w) -> CallCC (WriterT w m) a b
Lift a callCC operation to the new monad.
在库中找不到状态保持变体。相反,上面的变体在那里定义为
liftCallCC callCC f = WriterT $
callCC $ \ c ->
runWriterT (f (\ a -> WriterT $ c (a, mempty)))
注意 mempty
。如果我们有一个 get
操作,我们可以将 "current state" 存储在那里,这样它就不会在这个过程中丢失,但是如果我们有那个,我们将不再在 writer monad 中,而是在状态一。
另请注意,以相反顺序堆叠 monad 可实现我们想要的效果。
bar :: ContT String (Writer String) ()
bar = do
(stop,loop) <- getCC' False
if stop
then do lift $tell "bbb"
else do lift $ tell "aaa"
loop True
-- > runWriter (runContT bar (const $ pure ""))
-- ("","aaabbb")