显然MonadCont
s 比普通Monad
的 s 更受限制并且提供更多的权力,这要归功于它的callCC
. 这意味着它的实例更少,你可以用它做更多的事情。
查看 的已定义实例时MonadCont
,看起来那里列出的所有内容都需要一个Cont
或ContT
一个已经存在的MonadCont
实例。这意味着我们必须从一些开始,Cont
或者ContT
特别是不能IO
变成MonadCont
.
callCC
但是,我认为在上下文中使用是有意义的IO
,因此我们可以简化以下内容(根据官方 Hackage 页面 callCC
示例进行调整):
whatsYourName :: IO ()
whatsYourName = do
name <- getLine
let response = flip runCont id $ do
callCC $ \exit -> do
when (null name) (exit "You forgot to tell me your name!")
return $ "Welcome, " ++ name ++ "!"
print response
进入
whatsYourName' :: IO ()
whatsYourName' = do
name <- getLine
response <- callCC $ \exit -> do
when (null name) (exit "You forgot to tell me your name!")
return $ "Welcome, " ++ name ++ "!"
print response
以callCC
更清洁的方式在 do 块中使用。
当然,要创建IO
一个实例,MonadCont
我们必须有一些魔法,因为callCC
for 的IO
意思是“调用给定的函数,未来的计算指定现实世界中接下来会发生什么”,所以只有解释器或编译器才能真正知道这意味着什么. 另一方面,我没有看到任何理论上的理由表明这是可导入的,因为 Scheme 已经拥有它很长时间了,并且制作这样一个实例根本不需要更改语言。
可能的问题
我能想到的一个因素是 的语义callCC
与适当的清理保证相冲突。许多语言都提供了“try...finally”控制以进行适当的清理,C++ 的析构函数也保证了这一点。我不确定 Haskell 中它是什么,但如果callCC
可以IO
使用它来逃避任何IO
需要清理的相关上下文,因此提供 sush 保证将变得不可能,正如您可以看到Ruby 中发生的那样。
意见讨论
@jozefg 的回答非常好。我只想在这里写下我的看法。
确实
MonadCont
来自mtl。但这并不意味着 GHC 或其他编译器不能定义 aunsafeCallCC
并定义实例,如果MonadCont
正确的定义在编译模块的范围内-XIOMonadCont
并被设置。我已经谈到了异常安全,看起来很难确定这一点。但是,Haskell 已经有了,在我看来
unsafePerformIO
,这基本上比 更不安全。unsafeCallCC
在大多数情况下,原因
callCC
是过于强大,应尽可能避免。但是,在我看来,延续传递风格可以用来使惰性求值显式化,这有助于更好地理解程序,从而更容易找到可能的优化。当然 CPS 不是MonadCont
,但使用它并将深层嵌套的内部函数转换为 do 表示法是一个自然的步骤。