StateT over Cont。为什么我的州没有被重置?

时间:2017-07-08 16:47:03

标签: haskell monads monad-transformers continuations

我正在使用herethis SO question中描述的Cont monad技巧。

这个函数让你“跳回”到计算的前面,拿一个参数让你可以做不同的事情:

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问题中为explainedCont的效果优先于WriterT的效果。当我们重置计算时,日志丢失:

*Main> print $ runCont (execWriterT foo) id
"bbb"

第二个示例完全相同,只使用StateT而不是WriterT。但是,在这种情况下,日志会被保留!

*Main> print $ runCont (execStateT foo' "") id
"aaabbb"

这种差异的解释是什么?

1 个答案:

答案 0 :(得分:4)

(我觉得这不是一个完全令人满意的答案,但至少它应该澄清一点。)

我认为这是因为callCC被解除了。在州monad案例中,在将兔子追到洞口之后,我们遇到了这个:

liftCallCC :: CallCC m (a, s) (b, s) -> CallCC (StateT s m) a b
     

将callCC操作统一解除到新monad。这个版本   进入延续时回滚到原始状态。

liftCallCC' :: CallCC m (a, s) (b, s) -> CallCC (StateT s m) a b
     

将callCC操作原位提升到新monad。这个版本   在进入延续时使用当前状态。

采取哪一个?一个保留状态:

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

作家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
     

将callCC操作提升到新的monad。

在库中找不到保留状态的变体。相反,上面的变体被定义为

liftCallCC callCC f = WriterT $
    callCC $ \ c ->
    runWriterT (f (\ a -> WriterT $ c (a, mempty)))

请注意mempty。如果我们有一个get操作,我们可以在那里存储“当前状态”,这样它就不会在这个过程中丢失,但是如果我们拥有它,我们将不再是在编写器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")