3

显然MonadConts 比普通Monad的 s 更受限制并且提供更多的权力,这要归功于它的callCC. 这意味着它的实例更少,你可以用它做更多的事情。

查看 的已定义实例时MonadCont,看起来那里列出的所有内容都需要一个ContContT一个已经存在的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我们必须有一些魔法,因为callCCfor 的IO意思是“调用给定的函数,未来的计算指定现实世界中接下来会发生什么”,所以只有解释器或编译器才能真正知道这意味着什么. 另一方面,我没有看到任何理论上的理由表明这是可导入的,因为 Scheme 已经拥有它很长时间了,并且制作这样一个实例根本不需要更改语言。

可能的问题

我能想到的一个因素是 的语义callCC与适当的清理保证相冲突。许多语言都提供了“try...finally”控制以进行适当的清理,C++ 的析构函数也保证了这一点。我不确定 Haskell 中它是什么,但如果callCC可以IO使用它来逃避任何IO需要清理的相关上下文,因此提供 sush 保证将变得不可能,正如您可以看到Ruby 中发生的那样

意见讨论

@jozefg 的回答非常好。我只想在这里写下我的看法。

  1. 确实MonadCont来自mtl。但这并不意味着 GHC 或其他编译器不能定义 aunsafeCallCC并定义实例,如果MonadCont正确的定义在编译模块的范围内-XIOMonadCont并被设置。

  2. 我已经谈到了异常安全,看起来很难确定这一点。但是,Haskell 已经有了,在我看来unsafePerformIO,这基本上比 更不安全。unsafeCallCC

  3. 在大多数情况下,原因callCC是过于强大,应尽可能避免。但是,在我看来,延续传递风格可以用来使惰性求值显式化,这有助于更好地理解程序,从而更容易找到可能的优化。当然 CPS 不是MonadCont,但使用它并将深层嵌套的内部函数转换为 do 表示法是一个自然的步骤。

4

4 回答 4

9

我会说这是个坏主意。首先,MonadCont是在MTL。GHC 对此一无所知,这意味着使编译器依赖于 3rd 方库,ick。

其次,callCC即使在一些非常高调的计划者中也不受欢迎,主要是因为它使推理代码变得很痛苦!以同样的方式goto很难推理。尤其是在 Haskell 中,我们不得不担心的地方

  1. 异常安全吗?(这已经很难了)
  2. callCC安全吗?

最后,我们甚至不需要它。如果您想使用延续和 IO,请使用ContT IO,它同样强大。但是,我几乎可以保证它可以用功能不那么强大的东西来代替,比如monad-prompt. 延续是一把大锤,10 次中有 9 次callCC太强大了,使用高阶函数和惰性的组合可以更愉快地表达。

举个例子,一个典型的用途callCC是实现类似异常的东西,但是在 Haskell 中我们可以只使用 monads :)(它依赖于高阶函数和惰性)。

从本质上讲,您的提议会增加复杂性,这意味着将 MTL 合并到基础中,以及要避免的一大堆其他不愉快的事情liftIO

重新编辑

  1. 当然你可以做到这一点,但它MonadCont当然不是一个实例:)
  2. 这有点不同,unsafePerformIO旨在使其他任何人都看不到副作用,因为您无法保证执行操作的方式或时间。

    如果是这种情况callCCIO,那么您可以使用Cont

  3. 延续传递风格很有用,我们有它,用ConT r IO!这对我来说是棺材上最大的钉子,我认为这个想法与仅使用现有库而不是困难且可能不安全的编译器破解相比没有任何好处。

于 2013-11-08T05:20:07.663 回答
1

通过将ContTmonad 用于简单的中止功能,您实际上是在用火箭筒击中苍蝇。:-) 如果您的目标只是在出现问题时中止返回错误消息,那么您可能需要考虑改用ErrorTmonad(由transformers提供)。或者,如果发生错误时您不会对结果使用单独的类型,那么您可能需要考虑使用AbortTmonad(由AbortT-transformers提供)。

于 2013-11-08T05:38:26.947 回答
1

答案是否定的,因为 call/cc 对于任何语言来说都是一个不好的特性。虽然 call/cc 在 Scheme 中已经存在很长时间了,但这并不意味着 call/cc 是一个很好的语言特性。在 Scheme 中使用 call/cc 的经验表明它是一个糟糕的语言特性:使用 call/cc 经常会泄漏内存,没有动态风的 call/cc 不适合任何严肃的程序或任何库;另一方面,使用动态风,标准的 call/cc 习语会变慢。在http://okmij.org/ftp/continuations/against-callcc.html中列举了所有这些缺点,并附有支持代码和其他证据

我同意连续传递风格有很多好处。事实上,当我们以 monadic 风格编写 Haskell 代码时,我们使用的正是 CPS。实际上,考虑一个简单的嵌套函数调用putChar (getChar ())。我们在CPS中写成如下

 getCharK :: () -> (Char -> w) -> w
 putCharK :: Char -> (() -> w) -> w

 gp :: (() -> w) -> w
 gp = \k -> getCharK () (\c -> putCharK c k)

在这里,我们如何为 monadic getM 和 putM 编写相同的嵌套调用(对于某些 monad M):

getM :: () -> M Char
putM :: Char -> M ()

gpM :: M ()
gpM =  getM () `bind` (\c -> putM c)

现在,如果 bind 被定义为

bind m f = \k -> m (\x -> (f x) k)

那么单子嵌套函数应用程序与代码的 CPS 版本相同。如果在 bind 的定义中将 M 替换为ContT w IOand 和 newtype 包装器 ContT/runContT((>>=)在 ContT w IO monad 中),则 monadic 示例在 Haskell 中有效。所以,Haskell 已经让我们在 CPS 中编写代码;do-nonation 使它非常方便。

于 2013-11-23T03:50:08.613 回答
0

文档说:

由于 Haskell 的惰性语义,许多在其他语言中需要延续的算法在 Haskell 中不需要它们。滥用 Continuation monad 会产生无法理解和维护的代码

我想这可能是主要原因

于 2013-11-08T05:14:37.647 回答