24

在Kent Dybvig的 The Scheme Programming Language(第 4 版)第 3.4 节中,他非常清楚地描述了什么是延续传递风格。至于为什么,他给出了两个原因:

  1. 将多个结果传递给它的延续,因为实现延续的过程可以接受任意数量的参数。
  2. CPS 还允许一个过程采用单独的延续......,它可以接受不同数量的参数。

由于第一个原因也可以使用valuesprocedure 和第二个 using来完成case-lambda,我不清楚使用延续传递样式的优点。有人可以向我展示一些示例,说明哪些地方适合继续传递样式,哪些地方可以使代码更好、更清晰等?

4

2 回答 2

15

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

但是直接使用 CPS 样式仍然很有用:例如,正如他所提到的,您可以编写一个具有多个延续的函数,可能具有不同的 arrities——比如一个解析函数,它接收一个单输入函数来发送一个在解析失败时将值解析为要调用的空值失败函数(并且此函数可能会因错误或回溯而中止并尝试使用其他解析规则)。另一个可能的用途是当您想要准确控制延续中的内容而不是让call/cc获取完整上下文时。

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

于 2011-12-17T15:38:09.937 回答
8

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

...除了 的定义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 子形式。这些表示被用作模式匹配器的“延续”的表达式,用稍微宽松的术语来说。当所考虑的模式与所考虑的值匹配时,使用成功延续;如果没有,则使用失败继续。成功的延续取决于我们是否已经匹配了所有当前顶级模式,要么是一个匹配模式其余部分的表达式,要么是我们在模式完成匹配时执行的结果。当模式无法匹配时,使用失败延续,以便回溯到下一个选择点。

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

于 2011-12-18T01:43:46.890 回答