我正在尝试了解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解释器,并且看起来等待输入。
答案 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)
这是怎么回事:
x
和f
call/cc&
呼叫f
x
设置为r
(续a)x
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,则您永远不必返回。有趣的事情总是传递到下一个调用,直到程序停止。