【问题标题】:continuation passing style vs monads延续传球风格与单子
【发布时间】:2011-05-30 09:09:55
【问题描述】:

延续传递风格 (cps) 和 monad 有什么区别。

【问题讨论】:

    标签: functional-programming monads continuation-passing


    【解决方案1】:

    没有关系,因此这个问题与询问蓝色和冥王星之间的区别一样有意义。

    【讨论】:

    • 大声笑,我在想 monad 实现需要创建和返回 monads。
    • 我投了反对票,因为有很强的关系(见下面 sigfpe 的帖子):延续形成一个 monad,每个 monad 都可以用 continuation monad 表示:monadic bind type 可以写成m a -> Cont (m b) a。跨度>
    • 我也投了反对票。 CPS 和 Monad 非常相关。事实上,我怀疑这就是为什么大多数绑定定义都是m >>= k,如“k for continuation”
    【解决方案2】:

    【讨论】:

      【解决方案3】:

      The essence of functional programming中所述:

      使用 monad 进行编程让人想起 continuation-passing style (CPS),本文探讨了两者之间的关系。在某种意义上它们是等价的:CPS 是作为 monad 的一个特例出现的,任何 monad 都可以通过改变答案类型嵌入到 CPS 中。但是一元方法提供了额外的洞察力并允许更精细的控制。

      那篇论文相当严谨,实际上并没有完全扩展 CPS 和 monad 之间的关系。在这里,我试图给出一个非正式但说明性的例子:

      (注意:下面是一个新手(我自己)对 Monad 的理解,虽然在写完之后它看起来确实像是那些高代表用户的答案之一。请用大量的盐来接受它)

      考虑经典的Maybe monad

      -- I don't use the do notation to make it 
      -- look similar to foo below
      
      bar :: Maybe Int
      bar =
          Just 5 >>= \x ->
          Just 4 >>= \y ->
          return $ x + y
      
      bar' :: Maybe Int
      bar' =
          Just 5 >>= \x ->
          Nothing >>= \_ ->
          return $ x
      
      GHCi> bar
      Just 9
      GHCi> bar'
      Nothing
      

      所以一旦遇到Nothing,计算就会停止,这里没有什么新鲜事。让我们尝试使用 CPS 来模拟这种单子行为:

      这是我们使用 CPS 的原版 add 函数。我们在这里使用所有Int,而不是代数数据类型以使其更容易:

      add :: Int -> Int -> (Int -> Int) -> Int
      add x y k = k (x+y)
      
      GHCi> add 3 4 id
      7
      

      注意它与单子有多么相似

      foo :: Int
      foo =
          add 1 2 $ \x -> -- 3
          add x 4 $ \y -> -- 7
          add y 5 $ \z -> -- 12
          z
      
      GHCi> foo
      12
      

      好的。假设我们希望计算上限为 10。也就是说,当下一步导致值大于 10 时,任何计算都必须停止。这有点像说“Maybe 计算必须停止并返回 Nothing只要链中的任何值都是Nothing)。让我们看看我们如何通过编写“CPS 转换器”来做到这一点

      cap10 :: (Int -> Int) -> (Int -> Int)
      cap10 k = \x ->
          if x <= 10 
          then 
              let x' = k x in 
              if x' <= 10 then x' else x
          else x
      
      foo' :: Int
      foo' =
          add 1 2 $ cap10 $ \x -> -- 3
          add x 4 $ cap10 $ \y -> -- 7
          add y 5 $ cap10 $ \z -> -- 12
          undefined
      
      GHCi> foo'
      7
      

      请注意,最终返回值可以是undefined,但这很好,因为评估在第 3 步停止 (z)。

      我们可以看到cap10 用一些额外的逻辑“包装”了正常的延续。这与 monad 非常接近——将计算与一些额外的逻辑粘合在一起。

      让我们更进一步:

      (>>==) :: ((Int -> Int) -> Int) -> (Int -> Int) -> Int
      m >>== k = m $ cap10 k
      
      foo'' :: Int
      foo'' =
          add 1 2 >>== \x -> -- 3
          add x 4 >>== \y -> -- 7
          add y 5 >>== \z -> -- 12
          undefined
      
      GCHi> foo''
      7
      

      哇!也许我们刚刚发明了Cap10 monad!

      现在,如果我们查看 source code of Cont,我们会看到 Cont

      newtype Cont r a = Cont { runCont :: (a -> r) -> r }
      

      runCont 的类型是

      Cont r a -> (a -> r) -> r
      ((a -> r) -> r) -> (a -> r) -> r
      

      这与我们 &gt;&gt;== 的类型非常吻合

      现在来实际回答问题

      现在在输入所有这些之后,我重新阅读了原始问题。 OP要求“差异”:P

      我想不同之处在于 CPS 为调用者提供了更多控制权,而通常 monad 中的 &gt;&gt;= 完全由 monad 的作者控制。

      【讨论】:

        【解决方案4】:

        探讨这个问题的一篇有趣的论文是"Imperative functional programming",作者是 Peyton Jones 和 Wadler。

        这是介绍 monadic IO 的论文,它与包括 CPS 在内的替代方法进行了比较。

        作者总结:

        所以单子比延续更强大,但这仅仅是因为类型!目前尚不清楚这是否只是 Hindley-Milner 类型系统的产物,或者这些类型是否揭示了根本重要性的差异(我们自己的直觉是后者——但这只是一种直觉。)

        【讨论】:

        • 那个强大的定义不明确(如this)。如果所有感兴趣的只是表达性,那么不难得出结论,因为单子比论文中的延续暴露了“(相当)”更多的语言结构(因此做更多的事情)。更重要的神器实际上来自元级别:如果语言不是默认惰性的,那么没有太多理由展示 monadic 方法的力量,因为评估规则制定没有排除许多其他样式基础设施功能强大,非常容易。
        猜你喜欢
        • 1970-01-01
        • 2012-01-22
        • 2020-01-04
        • 1970-01-01
        • 2012-04-19
        • 1970-01-01
        • 2011-06-30
        • 2010-12-21
        • 1970-01-01
        相关资源
        最近更新 更多