【问题标题】:How to use continuations in scheme?如何在方案中使用延续?
【发布时间】:2019-09-11 06:43:07
【问题描述】:

我正在尝试了解 Scheme 中的 call/cc 运算符。我计划在我的 JavaScript lisp 中实现它。这是我的简单代码:

(letrec ((x 0)
         (f (lambda (r)
                (set! x r)
                (display (r 20))
                (display "10"))))
   (display (call/cc f))
   (x "30"))

我坚持它应该打印 20 然后 30 然后 10。但它会创建无限循环(它会继续打印 30)。这段代码应该如何显示 3 个值,调用 display 3 次?

是否可以创建不消耗堆栈的循环?

我找到了一些example on stack overflow,但这个根本不起作用:

(define x 0) ; dummy value - will be used to store continuation later

(+ 2 (call/cc (lambda (cc)
                (set! x cc)  ; set x to the continuation cc; namely, (+ 2 _)
                3)))         ; returns 5

(x 4) ; returns 6

它冻结了 100% CPU 的 guile 解释器,它看起来正在等待输入。

【问题讨论】:

  • “它冻结了 100% CPU 的 guile 解释器” - 不,它没有。至少在我看来,它按预期工作,最终 (x 4) 评估为 6

标签: scheme continuations callcc


【解决方案1】:

您是否 lisp 实现将用户代码转换为继续传递样式?在这种情况下,这很容易。 call/cc是这个:

(define (call/cc& f& continuation)
  (define (exit& value actual-continuation)
    (continuation value))
  (f& exit& continuation))

看看你的第一个代码,我认为它变成了这样:

((lambda (x k)
   ((lambda (f k)
      (call/cc& f (lambda (v) ; continuation a
                    (display& v (lambda (_) ; continuation b
                                  (x "30" k))))))
    (lambda (r k)
      (set!& x r (lambda (_) ; continuation c
                   (r 20 (lambda (v) ; continuation d
                           (display& v (lambda (_) ; continuation e
                                         (display& "10" k))))))))
    k)
   0
   halt)

这是发生了什么:

  • 我们制作xf
  • call/cc& 致电f
  • x 设置为 r(续一)
  • r 以 20 作为值调用
  • 继续 c 被忽略,而是用 20 调用继续 a
  • 显示 20,然后调用延续 b
  • b 用“30”调用x
  • 忽略延续 k,取而代之的是用 30 调用延续 a
  • 显示30,然后调用延续b
  • 转到“b 调用 x 并使用“30” 3 行并继续

所以打印“20”,然后“30”永远似乎是这段代码的正确结果。重要的是要注意它从不显示"10",因为它调用r并传递延续,但它被绕过到call/cc原始延续,即延续a。

至于实现。以前,所有 Scheme 实现都只是将代码转换为继续传递样式是很常见的,但今天更常见的是只做需要的部分。例如。 Ikarus 不做 CPS,但为了让call/cc 工作,它需要做它直到下一个继续提示。

在开头没有突变的情况下查看call/cc 可能会更好。例如。

(+ 2 (call/cc (lambda (exit)
                (+ 3 (* 5 (exit 11))))))

现在变成:

(call/cc& (lambda (exit k)
            (exit 11 (lambda (v)
                       (*& 5 v (lambda (v)
                                 (+& 3 v k))))))
          (lambda (v)
            (+& 2 v repl-display)))

现在我们知道exit 被调用,因此整个事情变成了:

((lambda (v) (+& 2 v repl-display)) 11)

显示13。现在将延续作为最后一个论点在纸上看起来不错。在想要支持可变参数的实现中,最好将延续作为第一个参数。

所有的延续都是尾调用,因此堆栈永远不会增长。事实上,如果使用完整的 CPS,您将永远不必返回。所有有趣的东西总是传递给下一个调用,直到程序停止。

【讨论】:

  • 谢谢 需要这个仔细阅读。函数名称末尾的 & 是什么?
  • @jcubic & 结尾只是命名约定,表明它需要继续。在实际实现中,如果语言总是进行 CPS 转换,原语 + 将继续使用,但在我的示例中,我使用 +& 表示它在 CPS 版本中是 +。您可能会从Matt Mights articles 中受益。
猜你喜欢
  • 2012-11-03
  • 2018-04-08
  • 2013-04-22
  • 1970-01-01
  • 2012-03-29
  • 1970-01-01
  • 2015-05-26
  • 2011-02-10
相关资源
最近更新 更多