转到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
之上。 当您切换ContT
和WriterT
,现在继续只解开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
低。 ErrorT
和ContT
之间的最大区别在于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保持最高也是很重要的。