continuation

377 阅读6分钟

what is continuation

刚开始学continuation真的头大,关于这个continuation定义,说什么的都有,什么继续点,control flow | func的call back

后来看到一段话才有点明白

a continuation is a return point,a call stack, a the remainder of the program,viewed as a function

continuation可以是什么继续点,control flow或者func的call back,但是它都是function。function是什么?我的理解是对计算过程procedure的抽象,换言之continuation就是procedure,不过是整个procedure的一部分。

不理解也没关系,举个例子就懂了,现在有一个procedure或者说函数 A

(+ 1 (* 2 3))

答案是7,很简单。这个计算分成两部分,a‘(+ 1 x)和a'’(* 2 3)两部分计算

对于a‘来说它的continuation就是a'’,就是两件事:处理和返回

也可以举一个现实的例子:二楼的厨师要给我做饭,但是他不知道我要吃什么?于是他给我打了个电话,然后我告诉他我喜欢红烧鸡翅,然后他就开始烤鸡翅了。(有没有感觉这个事情很蠢,为什么要这么做呢?因为事情的关键控制权在我,在这个里层的我,所以他要等我返回)

然后就可以优化了:我告诉厨师想吃红烧鸡翅,然后厨师就可以工作了。(这就是cps ,实现控制反转),但是关于cps我们先按下不表

在scheme中可以用call/cc来支持continuation这个特性,并且continuation可以作为第一等公民(first class),也就是可以作为函数的参数和返回值。

(call/cc *expr*) does the following:

  1. Captures the current continuation.

  2. Constructs a function C that takes one argument, and applies the current continuation with that argument value.

  3. Passes this function as an argument to *expr* --- i.e., it invokes (*expr* C).

  4. Returns the result of evaluating (*expr* C), unless *expr* calls C, in which case the value that is passed to C is returned.

    来源(courses.cs.washington.edu/courses/cse…

搞不懂没关系,语法上反正就是先call/cc 再来一个匿名函数f,这个函数的参数k,这个k就是continuation。

在行为上:call/cc会先在函数中占着一个坑(hole),在去捕获下一个procedure作为continuation,然后把capture的procedure的值填回到原来的hole,再继续原本函数的执行。(有点懵,没事看下面的例子)

continuation as first class in scheme

(define a 5)
(define (add1)
  (+ 1
  (call/cc
   (lambda (k) 
     (set! a k)))))
-> a
-> 5-> (add1)
->error-> a
-> procedure-> (a 1)
-> 2-> (a (+ 2 3))
-> 6

刚开时a还是5,运行add1后,可以看到我们把k赋给了a,a就是continuation了,还有一件事整个

(call/cc
   (lambda (k) 
     (set! a k))))

都会变成hole。然后执行(a (+ 2 3)),就会capture这个(+ 2 3)procedure然后就会把这个procedure的值5填回到hole,再继续原来函数的执行就是(+ 1 5),结果是6

反正call/cc就干两件事,先把后面的procedure抽象成continuation,再去capture他,算出值再把值填到hole,继续func的计算(注意是继续有关call/c及其之后的计算),在回去时env也切换到原来的env时

再去看那个厨师的例子,其实和这个概念是一样的

还有一件事:如果fn中没有调用continuation,则(call/cc ...)的值就是函数的返回值。

上面关于只会继续有关call/c及其之后的计算,可能是一个空洞的概念。我们可以举个例子

(define a 5)
(define (add1)
  (display "cant go here")
  (+ 1
  (call/cc
   (lambda (k) 
     (set! a k))))
  (display "hi")
  )
​
  ->(add1)
  ->error
  
  ->(a 1)
  ->hi
​

让chatgpt来解释什么是continuation

image-20230330000528439 感觉就是我想讲的

(新加的):今天去看了eopl,上面说continuation

the continuation of an expression represents a procedure that takes the result of the expression and completes the computation

我只能说这句话杀死比赛了

难怪在( + 1 (* 2 3))中(+ 1)的continuation是

(lambda(x) (+ 1 x))

image-20230330201202603

a small test

((call/cc (lambda (cont) cont)) (lambda (x) "hi"))
​
-> hi

不知道如何下手?先抽象成(f a),就要解析f和a了。

先看f:他没有调用continuation,所以回返回cont(就是一个continuation),a就是一个函数

接下来就是执行f(a)了:cont会capture然后填充到hole,再继续计算

于是代码变成了现在这样

((lambda (x) "hi") (lambda (x) "hi"))

自然结果是hi(在scheme中函数也是first class的)

yin yang

(let* ((yin ((lambda (foo) (display "@") foo)
             (call/cc (lambda (bar) bar))))
       (yang ((lambda (foo) (display "*") foo)
              (call/cc (lambda (bar) bar)))))
  (yin yang))
​
-> @*@**@***@****@*****@******

感觉有点晕没关系,let we do it

让我们一行一行的来看:

1.lambda (foo) (display "@") foo 就打印一个@

2.yin被赋值为c1(continuation 1)

3.(lambda (foo) (display "*") foo 打印出 *

4.yang被赋值为c2(continuation 2)

5.(yin yang),就像small test一样,分别解析yin yang,并且由于第一行和第三行的原因,会打印出@ *

现在 yin:c1 yang:c2 ->(terminal |之后表示是新产生的)| @ *

现在就变成了(c1 c2)

代码就会变成这样(不要在意括号)

  yin ((lambda (foo) (display "@") foo
       lambda (foo) (display "*") foo
       (call/cc (lambda (bar) bar))
  yang (call/cc (lambda (bar) bar)
 (yin yang)

刚开始我还在想他为什么会继续计算下去呢?后来觉得是因为他们都在let*中。

现在 yin :c2(为什么yin是c2吗?因为c1 apture的c2) yang:c 3(为什么yang是c3吗?重新生成的) -> :@ *| @ *(为什么不是@ * *呢?不是解析yang也会打印 * 吗? 因为yang现在还是一个continuation)

计算(c2 c3)

代码就会变成这样(不要在意括号)

yang ((lambda (foo) (display "*") foo)
       (call/cc (lambda (bar) bar)
(yin yang

yin:c1(为什么yin是c1吗?因为在c2下yin就是c1) yang:c3 -> @ * @ *| *

(c1 c3)

代码就会变成这样(不要在意括号)

  yin ((lambda (foo) (display "@") foo
       lambda (foo) (display "*") foo
       (call/cc (lambda (bar) bar))
  yang (call/cc (lambda (bar) bar)
 (yin yang)

yin: c3 yang:c4 ->@ * @ * * | @ *

(c3 c4)

代码就会变成这样(不要在意括号)

yang ((lambda (foo) (display "*") foo)
       (call/cc (lambda (bar) bar)
(yin yang

yin:c2(为什么yin是c2吗?因为在c3下yin就是c2) yang:c4 -> @ * @ * * @ * |*

(c2 c4)

代码就会变成这样(不要在意括号)

yang ((lambda (foo) (display "*") foo)
       (call/cc (lambda (bar) bar)
(yin yang

yin:c1(为什么yin是c1吗?因为在c2下yin就是c1) yang:c4 -> @ * @ * * @ * * | *

(c1 c4)

代码就会变成这样(不要在意括号)

  yin ((lambda (foo) (display "@") foo
       lambda (foo) (display "*") foo
       (call/cc (lambda (bar) bar))
  yang (call/cc (lambda (bar) bar)
 (yin yang)

yin:c4(为什么yin是c4吗?因为c1 apture的c4) yang:c5(重新生成的) ->@ * @ * * @ * * * | @ *

(c4 c5)

......

总结下来,关于yin有两种情况:1 capture新的continuation 2. 是一个continuation apply 一个continuation

yang也有两种情况:1. 旧的传递的 2.重新生成的

参考资料

1.柯里化的前生今世(七):first-class continuation(柯里化的前生今世(七):first-class continuation - 知乎 (zhihu.com)

2.scheme心得(1) continuation与阴阳谜题(scheme心得(1) continuation与阴阳谜题 - CodeAntenna