如何在方案中使用延续?

时间:2019-04-22 01:30:29

标签: scheme continuations callcc

我正在尝试了解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个值,调用显示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解释器,并且看起来等待输入。

1 个答案:

答案 0 :(得分:0)

您执行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(续a)
  • r以20作为值被调用
  • continuation c被忽略,取而代之的是a继续为20
  • 显示20,然后调用延续b
  • b用“ 30”呼叫x
  • 连续性k被忽略,取而代之的是连续性30。
  • 显示30,然后调用延续b
  • 转到“ b呼叫x,其中“ 30” 3行并继续

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

关于实现。在所有Scheme实现中,通常都只是将代码转换为延续传递样式,但是如今,仅执行所需的部分更为普遍。例如。 Ikarus不会执行CPS,但是要使call/cc起作用,它需要这样做,直到下一个继续提示。

最好先看看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) 。现在,在最后一个论点上继续使用延续在纸上看起来不错。在要支持varargs的实现中,最好将延续性作为第一个参数。

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