为什么延续传球风格

时间:2011-12-17 10:33:29

标签: scheme continuations continuation-passing

在肯特Dybvig的The Scheme Programming Language(第4版)section 3.4中,他非常清楚地描述了 继续传递风格是什么。对于为什么,他提出了两个理由:

  1. 将多个结果传递给它的continuation,因为实现continuation的过程可以接受任意数量的参数。
  2. CPS还允许一个过程采用单独的延续......,它可以接受不同数量的参数。
  3. 由于第一个原因也可以使用values过程完成,第二个原因使用case-lambda,我不清楚使用连续传递样式的优点。有人可以告诉我一些关于延续传递风格适当的例子,它使代码变得更好,更清晰等等吗?

2 个答案:

答案 0 :(得分:13)

Dybvig使用本节中的显式延续来激励call/cc作为语言的一部分。当他提到在没有它的情况下编写代码需要对所使用的所有代码进行全局转换(包括您调用的函数)时,主要观点是在该部分的末尾附近。因此,在Scheme中,您通常使用宏构建自己的构造,而continuation是这些有用的构造之一 - 但是您不能通过宏实现它们,因为它们只实现了局部转换。

但直接使用CPS样式仍然有用:例如,正如他所提到的,你可以编写一个具有多个延续的函数,可能具有不同的arrities - 就像一个接收单输入函数的解析函数发送解析值和解析失败时调用的nullary失败函数(此函数可能会因错误或回溯而中止并尝试使用其他解析规则)。另一种可能的用途是当你想要精确控制延续的内容时,而不是让call/cc抓住完整的上下文。

还有一种明显的例子,即用一种没有一流延续的语言编写代码,使得CPSed代码成为你唯一的选择。这方面的一个例子是许多使用IO的node.js程序,并且几乎强迫您在显式CPS中编写代码。

答案 1 :(得分:6)

  

由于第一个原因也可以使用values过程完成,第二个使用case-lambda我不清楚使用continuation传递样式的优点。

...除了values的定义指定它用多个参数调用它的延续。

我最喜欢的延续传递风格有用的问题的例子是编写模式匹配器。这是一种类似case类固醇的宏;它需要一个值并尝试将其结构与由对,符号(代表变量)和非符号原子(代表值)构建的一系列模式相匹配。如果匹配成功,则它将模式中的标识符绑定到值的相应子部分,并为该模式执行结果。如果失败,则尝试下一个模式。

以连续传递方式编写这种宏非常简单,使用两个不同的延续来表示“如果匹配成功该怎么办”(成功延续)和“如果匹配失败该怎么办” (失败的延续)。

获取我曾经写过的模式匹配宏的这个(简化的)片段(如果你不知道语法案例或语法规则,我道歉;因为我在运行中对其进行了调整,我当然希望它也有效! )。我将专注于匹配一对模式的规则。这是一种由两种图案组成的图案,头部图案和尾部图案;它匹配头部与头部图案匹配且尾部与尾部图案匹配的对。

;;;
;;; Outer "driver" macro; the meat is in pmatch-expand-pattern.
;;;
(define-syntax pmatch
  (syntax-rules ()
    ((pmatch value-expr (pattern . exprs) . clauses)
     (let* ((value value-expr)
            (try-next-clause
             (lambda () (pmatch value . clauses))))
       (pmatch-expand-pattern pattern
                              value
                              ;; success-k
                              (begin . exprs)
                              ;; failure-k
                              (try-next-clause))))))

(define-syntax pmatch-expand-pattern
  (lambda (stx)
    (syntax-case stx ()

      ;; Cases for constants and quoted symbols omitted, but they're trivial.

      ;; Match a pair pattern.  Note that failure-k is expanded three times; 
      ;; that's why pmatch encapsulates its expansion inside a thunk!
      ((pmatch-expand-pattern (head-pat . tail-pat) value success-k failure-k)
       (syntax
        (if (pair? value)
            (pmatch-expand-pattern head-pat 
                                   (car value)
                                   ;; If we successfully match the head, then
                                   ;; the success continuation is a recursive
                                   ;; attempt to match the tail...
                                   (pmatch-expand-pattern tail-pat
                                                          (cdr value)
                                                          success-k 
                                                          failure-k)
                                   failure-k))
            failure-k))

      ;; Match an identifier pattern.  Always succeeds, binds identifier
      ;; to value
      ((pmatch-expand-pattern identifier value success-k failure-k)
       (identifier? (syntax identifier))
       (syntax (let ((identifier value)) success-k)))
      )))

注意pmatch-expand-pattern宏表达式中的success-k和failure-k子表单。这些表示用作模式匹配器的略微宽松术语的“延续”的表达式。当所考虑的模式与所考虑的值匹配时,使用成功继续;如果没有,则使用失败继续。成功的延续取决于我们是否匹配了所有当前的顶级模式,要么是与模式的其余部分匹配的表达式,要么是在模式完成匹配时执行的结果。当模式无法匹配时,将使用失败延续,以便回溯到下一个选择点。

正如我所提到的,在上面的代码中使用了“continuation”这个术语,因为我们使用表达式作为continuation。但这只是关于如何将其作为宏实现的细节 - 该算法可以纯粹在运行时实现,实际过程作为延续。 (另外,try-next-clause程序是字面意义上的延续。)