Scheme 中的 continuation
continuation
是一个非常抽象晦涩的概念,为了理解这个概念,翻阅了大量的资料。
下面记录一些对 continuation
的粗浅的理解。
continuation
代表了程序于某一点接下来将要执行的 “后续部分”。
在 scheme 中,操作符 all‑with‑current‑continuation
(下文开始使用缩写 call/cc
) 提供了使用 continuation
的方法。call/cc
会捕获调用处的 continuation
,然后将该 continuation
传入其参数(一个 procedure)中进行处理。
call/cc
接受一个 procedure(过程/函数)作为参数,call/cc
调用处的continuation
将作为该 procedure 的参数传入。
观察这个例子:
1
(+ 1 (call/cc (lambda (k) (+ 2 (k 3)))))
例子中,
- 首先,我们先把
call/cc
所处位置部分当作一个“空洞” - 然后,
call/cc
所处位置的continuation
,就是 “空洞” 之外的程序后续将执行的过程
1
(+ 1 空洞)
具体点,就是 “一个将对该(空洞)位置做加一的过程”,相当于:
1
(lambda (hole) (+ 1 hole))
再来观察上述例子中 call/cc
的参数部分,
1
(lambda (k) (+ 2 (k 3)))
其中形参 k
绑定的即为 continuation
。执行时,该 continuation
会应用在 3
上。换句话说,3
充当了上述的“空洞”的值,继续走完 “下半程”(即 +1 操作)。
1
(+ 1 3)
结果为 4
。
escaping continuation
需要注意的是,上述过程中,3
“走了不寻常的路” 穿梭到 k
这个 continuation
中去了。
而原本 (k 3)
处的 continuation
(即 (+ 2 空洞)
)将被抛弃!就是说,开头的例子中,最终的运行结果就是 4
:
1
2
(+ 1 (call/cc (lambda (k) (+ 2 (k 3)))))
=> 4
这是个很有用的特性,叫做 escaping continuation
,可以用于退出一个计算过程,在这个例子中就是退出 + 2
的运算过程。可以用下面的代码加深理解:
1
2
3
4
5
6
7
8
9
10
11
12
13
;; 返回
(+ 1 (call/cc (lambda (return) (+ 2 (return 3)))))
=> 4
;; 退出
(call-with-current-continuation
(lambda (exit)
(for-each (lambda (x)
(if (negative? x)
(exit x)))
'(54 0 37 -3 245 19)) #t))
=> -3
作为比较:
1
2
(+ 1 (call/cc (lambda (k) (+ 2 3)))))
=> 6
该例子中,没有使用 call/cc
处的 continuation
,因此不会有逃逸现象,(+ 2 3)
的值作为 call/cc
的返回值,然后 + 1
,最终结果为 6
。
scheme 的 continuation
还能回到上一次离开的上下文,因为 continuation
里面,一直持有程序的“后续”,我们可以不限制的多次执行它们,用下面的例子来说明:
1
2
3
4
5
6
7
(define r #f)
(+ 1 (call/cc (lambda (k)
;; 将 `continuation` 保存在 `r` 中
(set! r k)
(+ 2 (k 3)))))
=> 4
这次的例子中,我们额外使用全局变量 r
来记录这个 continuation
,该例子此时跟之前一样,仍然返回运算结果 4
。
然后,我们再通过全局变量 r
来再次“重放”该 continuation
:
1
2
(r 5)
=> 6
再次强调,需要注意伴随 r
的逃逸现象,如下面的例子:
1
2
(+ 3 (r 5))
=> 6
其中 r
被应用在 5
时,(r 5)
本身的 continuation
就会被抛弃。