转到Haskell:任何人都可以解释这个继续monad使用这个看似疯狂的效果吗?

从这个线程(Control.Monad.Cont fun,2005),Tomasz Zielonka介绍了一个函数(托马斯·耶格(ThomasJäger)以一种清晰而美好的方式进行了评论)。 Tomasz接受一个callCC主体的参数(一个函数),并将其返回给以后使用的两个定义:

import Control.Monad.Cont ... getCC :: MonadCont m => m (ma) getCC = callCC (\c -> let x = cx in return x) getCC' :: MonadCont m => a -> m (a, a -> mb) getCC' x0 = callCC (\c -> let fx = c (x, f) in return (x0, f)) 

Haskellwiki也提到了这些 。 使用它们,你可以像Haskell中的goto semantics看起来很酷:

 import Control.Monad.Cont getCC' :: MonadCont m => a -> m (a, a -> mb) getCC' x0 = callCC (\c -> let fx = c (x, f) in return (x0, f)) main :: IO () main = (`runContT` return) $ do (x, loopBack) <- getCC' 0 lift (print x) when (x < 10) (loopBack (x + 1)) lift (putStrLn "finish") 

这将打印数字0到10。

这里有趣的一点。 我和Writer Monad一起用来解决某个问题。 我的代码如下所示:

 {-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, UndecidableInstances #-} import Control.Monad.Cont import Control.Monad.Writer getCC :: MonadCont m => m (ma) getCC = callCC (\c -> let x = cx in return x) getCC' :: MonadCont m => a -> m (a, a -> mb) getCC' x0 = callCC (\c -> let fx = c (x, f) in return (x0, f)) -- a simple monad transformer stack involving MonadCont and MonadWriter type APP= WriterT [String] (ContT () IO) runAPP :: APP a -> IO () runAPP a= runContT (runWriterT a) process where process (_,w)= do putStrLn $ unlines w return () driver :: Int -> APP () driver k = do tell [ "The quick brown fox ..." ] (x,loop) <- getCC' 0 collect x when (x<k) $ loop (x+1) collect :: Int -> APP () collect n= tell [ (show n) ] main :: IO () main = do runAPP $ driver 4 

当你编译并运行这段代码时,输​​出是:

 The quick brown fox ... 4 

在这个例子的深度黑暗中,零到三个数字被吞噬在某个地方。

现在,在“真实世界Haskell”奥沙利文,戈尔岑和斯图尔特说

“叠加变压器就像构成函数一样,如果我们改变函数的顺序,然后得到不同的结果,我们也不会感到惊讶,因此也是单核变换器。 (真实世界哈斯克尔,2008年,第442页)

我想出了将上面的变形金刚换掉的想法:

 --replace in the above example type APP= ContT () (WriterT [String] IO) ... runAPP a = do (_,w) <- runWriterT $ runContT a (return . const ()) putStrLn $ unlines w 

但是,这将不会编译,因为在Control.Monad.Cont中没有MonadWriter的实例定义(这就是为什么我最近问这个问题 。)

我们添加一个实例离开监听并通过undefined:

 instance (MonadWriter wm) => MonadWriter w (ContT rm) where tell = lift . tell listen = undefined pass = undefined 

添加这些行,编译并运行。 所有数字都打印出来。

在前面的例子中发生了什么?

这是一个有点非正式的答案,但希望有用。 getCC'返回当前执行点的延续; 你可以把它看作是保存一个堆栈帧。 getCC'返回的延续不仅具有调用时的ContT状态,而且还具有堆栈上的ContT之上的任何monad的状态。 当您通过调用continuation来恢复该状态时,在ContT之上ContT所有monad将在getCC'调用点返回到它们的状态。

在第一个示例中,使用type APP= WriterT [String] (ContT () IO) ,以IO为基本monad,然后是ContT ,最后是WriterT 。 所以当你调用loop ,作者的状态被解开到了getCC'调用,因为作者在monad栈上的ContT之上。 当您切换ContTWriterT ,现在继续只解开ContT monad,因为它比写入者高。

ContT不是唯一可能导致类似问题的monad变压器。 以下是ErrorT类似情况的一个例子

 func :: Int -> WriterT [String] (ErrorT String IO) Int func x = do liftIO $ print "start loop" tell [show x] if x < 4 then func (x+1) else throwError "aborted..." *Main> runErrorT $ runWriterT $ func 0 "start loop" "start loop" "start loop" "start loop" "start loop" Left "aborted..." 

即使作者monad被告知值,当内部ErrorT monad运行时,它们全部被丢弃。 但是如果我们切换变压器的顺序:

 switch :: Int -> ErrorT String (WriterT [String] IO) () switch x = do liftIO $ print "start loop" tell [show x] if x < 4 then switch (x+1) else throwError "aborted..." *Main> runWriterT $ runErrorT $ switch 0 "start loop" "start loop" "start loop" "start loop" "start loop" (Left "aborted...",["0","1","2","3","4"]) 

在这里,作者monad的内部状态被保存下来,因为它比monad栈上的ErrorT低。 ErrorTContT之间的最大区别在于ErrorT的types清楚地表明,如果抛出错误,任何部分计算都将被丢弃。

ContT位于堆栈的顶端时, ContT是绝对简单的,但有时可以将ContT展开到已知点。 例如,可以用这种方式实现一种交易。

我花了一些时间在λ演算中跟踪这个问题。 它生成的页面和页面的派生,我不会尝试在这里重现,但我确实了解monad堆栈如何工作。 你的types扩展如下:

 type APP a = WriterT [String] (ContT () IO) a = ContT () IO (a,[String]) = ((a,[String]) -> IO()) -> IO() 

您可以类似地扩展Writer的return >>= ,并与Cont的return >>=callCC 。 追踪它是非常乏味的。

在驱动程序中调用loop的效果是放弃正常延续,而是再次从调用返回到getCC' 。 被遗弃的延续包含了将当前x添加到列表中的代码。 因此,我们重复循环,但现在x是下一个数字,只有当我们击中最后一个数字(因此放弃延续),我们是否将["The quick brown fox"]["4"]

正如“真实世界Haskell”强调的IO单子需要留在堆栈的底部,继续monad保持最高也是很重要的。