从this线程(Control.Monad.Cont fun,2005),Tomasz Zielonka介绍了一个函数(由ThomasJäger以清晰而美观的方式评论)。 Tomasz接受callCC主体的参数(函数)并将其返回以供以后使用,具有以下两个定义:
import Control.Monad.Cont
...
getCC :: MonadCont m => m (m a)
getCC = callCC (\c -> let x = c x in return x)
getCC' :: MonadCont m => a -> m (a, a -> m b)
getCC' x0 = callCC (\c -> let f x = c (x, f) in return (x0, f))
Haskellwiki中也提到了这些。使用它们,你可以像haskell中的goto语义看起来非常酷:
import Control.Monad.Cont
getCC' :: MonadCont m => a -> m (a, a -> m b)
getCC' x0 = callCC (\c -> let f x = 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 (m a)
getCC = callCC (\c -> let x = c x in return x)
getCC' :: MonadCont m => a -> m (a, a -> m b)
getCC' x0 = callCC (\c -> let f x = 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
在这个例子的深刻黑暗中,数字0到3被吞噬了。
现在,在“真实世界哈斯克尔”中,奥沙利文,格尔岑和斯图尔特表示
“堆叠monad变换器类似于组合函数。如果我们改变我们应用函数的顺序然后得到不同的结果,我们就不会感到惊讶。所以它也适用于monad变换器。”< / em>(Real World 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的实例定义(这就是我最近问过this question的原因。)
我们添加一个实例,离开listen并传递undefined:
instance (MonadWriter w m) => MonadWriter w (ContT r m) where
tell = lift . tell
listen = undefined
pass = undefined
添加这些行,编译并运行。打印所有数字。
上一个例子中发生了什么?
答案 0 :(得分:34)
这是一个有点非正式的答案,但希望有用。 getCC'
返回当前执行点的延续;您可以将其视为保存堆栈帧。 getCC'
返回的延续不仅在调用时具有ContT
的状态,而且在堆栈上的ContT
以上的任何monad的状态。当您通过调用continuation恢复该状态时,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
的类型清楚地表明,如果抛出错误,任何部分计算都将被丢弃。
当ContT
位于堆栈顶部时,理由更简单,但有时能够将monad展开到已知点是有用的。例如,可以以这种方式实现一种交易。
答案 1 :(得分:11)
我花了一些时间在λ演算中追踪这个。它生成了我不会尝试在这里重现的派生页面和页面,但我确实对monad堆栈的工作方式有了一些了解。您的类型扩展如下:
type APP a = WriterT [String] (ContT () IO) a
= ContT () IO (a,[String])
= ((a,[String]) -> IO()) -> IO()
您可以类似地扩展作家的return
,>>=
和tell
,以及Cont的return
,>>=
和callCC
。追踪它是非常乏味的。
在驱动程序中调用loop
的效果是放弃正常延续,而是再次从调用getCC'
返回。放弃的延续包含将当前x
添加到列表中的代码。所以相反,我们重复循环,但现在x
是下一个数字,只有当我们达到最后一个数字(因此不放弃延续)时,我们才拼凑来自["The quick brown fox"]
和["4"]
的列表。
就像“真实世界Haskell”强调IO monad需要保持在堆栈的底部一样,延续monad仍然保持最佳状态似乎也很重要。