简体   繁体   English

如何在计划中使用延续?

[英]How to use continuations in scheme?

I'm trying to understand call/cc operator in Scheme. 我正在尝试理解Scheme中的call / cc运算符。 I'm planing of implementing this in my JavaScript lisp. 我正计划在我的JavaScript lisp中实现它。 This is my simple code: 这是我的简单代码:

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

I tough that it should print 20 then 30 then 10. But it create infinite loop (it keep printing 30). 我认为它应该打印20然后30然后10。但它创建无限循环(它继续打印30)。 How this code should look like to display 3 values, call display 3 times? 这段代码应该如何显示3个值,调用显示3次?

Should it be possible to create loops that don't consume stack with continuations? 是否可以创建不使用continuation的堆栈的循环?

I've found some example on stack overflow but this one don't work at all: 我已经找到了一些关于堆栈溢出的例子但是这个根本不起作用:

(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

it freezes the guile interpreter with 100% CPU and it look it waiting for input. 它用100%的CPU冻结了guile解释器,看起来它在等待输入。

Does you lisp implementation transform user code to continuation passing style? 你的lisp实现是否将用户代码转换为继续传递样式? In that case it is easy peasy. 在这种情况下,它很容易腻。 call/cc is this: call/cc是这样的:

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

Looking at your first code I think it becomes something like this: 看看你的第一个代码,我觉得它变成了这样的:

((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)

Here is whats happening: 这是最新发生的事情:

  • We make x and f 我们制作xf
  • call/cc& calls f call/cc&呼叫f
  • x is set to r (continuation a) x设置为r (续a)
  • r gets called with 20 as value r以20作为值被调用
  • continuation c is ignore, instead continuation a is called with 20 继续c是忽略,而是用20调用继续a
  • 20 gets displayed, then continuation b gets called 显示20,然后调用继续b
  • b calls x with "30" b用“30”调用x
  • continuation k is ignored, instead continuation a is called with 30 忽略连续k,而是用30调用continuation a
  • 30 gets displayed, then continuation b gets called 显示30,然后调用继续b
  • go to "b calls x with "30" 3 lines up and continue 转到“b呼叫x ,“30”3行,然后继续

So print "20", then "30" forever seems to be the correct result for this code. 所以打印“20”,然后“30”永远似乎是这个代码的正确结果。 It's important to notice that it will never display "10" since it calls r and passes the continuation but it gets circumvented to call/cc original continution which is continuation a. 重要的是要注意它永远不会显示"10"因为它调用r并通过延续,但它被绕过来call/cc原始连续,这是继续a。

As for implementations. 至于实施。 Before it was quite common for all Scheme implementations to just transform the code to continuation passing style, but today it's more common to only do the parts which are required. 在此之前,所有Scheme实现都只是将代码转换为继续传递样式,但是现在更常见的是只执行所需的部分。 Eg. 例如。 Ikarus does not do CPS, but in order for call/cc to work it needs to do it until the next continuation prompt. Ikarus没有做CPS,但是为了使call/cc能够工作,它需要在下一个继续提示之前完成。

It's probably better to look at call/cc without mutation in the beginning. 最好在开始时查看没有变异的call/cc eg. 例如。

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

Now this turns into: 现在变成:

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

Now we know exit gets called and thus this whole thing turns into: 现在我们知道exit被调用,因此整个事情变成:

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

Which displays 13 . 哪个显示13 Now having the continuation as last argument looks good on paper. 现在继续作为最后一个论点在纸上看起来很好。 In an implementation that wants to support varargs it's probably best that the continuation is the first argument. 在一个想要支持varargs的实现中,延续可能是最好的第一个参数。

All continuations are tail calls and thus the stack is never grown. 所有延续都是尾调用,因此堆栈永远不会增长。 In fact if full CPS is used you never have to return ever. 事实上,如果使用完整的CPS,您永远不必返回。 Everything interesting is always passed to the next call until the program halts. 一切有趣的内容总是传递给下一个调用,直到程序停止。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM