continuation
References
- 《The Scheme Programming Language, 4th Edition》 by 未知
- http://blog.sina.com.cn/s/blog_4dff871201018wtz.html
TODO
- https://www.zhihu.com/question/61222322/answer/564847803
- https://zhuanlan.zhihu.com/p/49117340
- https://www.sczyh30.com/posts/Functional-Programming/call-with-current-continuation/
- https://www.zhihu.com/question/21954238/answer/1829986581
Intro
Scheme 是第一个提供 conitnuation 的语言。
对于 Scheme 在整个表达式求值的过程中,任何一个子表达式都对应一个 contination ,其表示该子表达式求值完成后继续完成整个表达式的求值的过程。
Scheme 提供过程 call/cc 以捕获任何表达式对应的 continuation ,使用方法为 (call/cc p)
,其中 p 为一个过程,接收 call/cc
捕获的 current continuation (简称 cc) 作为入参,调用 call/cc
时,其捕获 cc 并应用在 p 上,将应用 cc 到 p 上求得的值作为其自身应用求得的值(前提是 cc 没被应用)。捕获到的 cc 是一个过程,可以应用一个参数 v 到 cc 上,若如此则程序的 current continuation 会替换为 cc ,并将 v 作为捕获 cc 时对应的子表达式的返回值。
注意到 contination 是一个过程,同时可以作为参数被传递,contination 在 Scheme 中是 first-class 的。
During the evaluation of a Scheme expression, the implementation must keep track of two things:
- what to evaluate and
- what to do with the value.
We call “what to do with the value” the continuation of a computation.
The continuation represents an entire (default) future for the computation.
在 Scheme 中 continuation 的实现要求 Scheme 过程调用保存为一颗树(而不是基于栈帧语言的线性关系),如果某个节点被 call/cc 捕获,则这个节点不能及时回收,所以要求垃圾回收机制。
continuation 的类型 TODO
continuation 可以实现 monad TODO
Examples
非本地退出(Non-local exit)
非本地退出:在 p 中应用了传入的 cc 。
for break
(define (test element cc)
(if (zero? element)
(cc 'found-zero) ;; non-local exist
#f))
(define (search-zero test lst)
(call/cc
(lambda (return)
(for-each (lambda (element)
(test element return))
lst)
#f)))
(search-zero test '(-3 -2 -1 0 1 2 3))
使用 cc 保存 break 时跳转到的位置。
generator
(define (for-each proc items)
(define (iter things)
(cond ((null? things))
(else
(proc (car things))
(iter (cdr things)))))
(iter items))
(define (generate-one-element-at-a-time lst)
;; Hand the next item from a-list to "return" or an end-of-list marker
(define (control-state return)
(for-each
(lambda (element)
(call/cc
(lambda (resume-here)
;; Grab the current continuation
(set! control-state resume-here) ;; !!!
(return element)))) ;; return elem
lst)
(return 'end))
(define (generator)
(call/cc control-state)) ;; return point as cc
;; Return the generator
generator)
(define generate-digit (generate-one-element-at-a-time '(0 1 2)))
(generate-digit)
这个 case 使用了两个cc,一个保存 generator 的返回地点用于非本地退出,一个保存下次 generator 该从 for-each 的哪次迭代中恢复,注意到 control-state 第一次是原始的过程,后面则变成了 contination。
非引用透明性
(define (get-cc) (call/cc (lambda (c) c)))
(define x (get-cc))
(x 10)
((get-cc) 10)
(define (get-cc) (call/cc (lambda (c) c)))
(let ([x (get-cc)]) (x (lambda (unused) "message")))
(define (get-cc) (call/cc (lambda (c) c)))
(((get-cc) (lambda (x) x)) "message")
任务切换
SPSC
(define dish #f)
(define (put! fruit) (set! dish fruit))
(define (get!) (let ([fruit dish]) (set! dish #f) fruit))
(define (consumer do-other-stuff)
(let loop ()
(when dish
(display (get!)) (newline)
(set! do-other-stuff (call/cc do-other-stuff))
(loop))))
(define (producer do-other-stuff)
(for-each (lambda (fruit)
(put! fruit)
(set! do-other-stuff (call/cc do-other-stuff)))
'("apple" "strawberry" "peach" "grape")))
(producer consumer)
Multi-tasks
(define lwp-list '())
(define lwp
(lambda (thunk)
(set! lwp-list (append lwp-list (list thunk)))))
(define start
(lambda ()
(let ([p (car lwp-list)])
(set! lwp-list (cdr lwp-list))
(p))))
(define pause
(lambda ()
(call/cc (lambda (k) (lwp (lambda () (k #f))) (start)))))
(lwp (lambda () (let f () (pause) (display "h") (f))))
(lwp (lambda () (let f () (pause) (display "e") (f))))
(lwp (lambda () (let f () (pause) (display "y") (f))))
(lwp (lambda () (let f () (pause) (display "!") (f))))
(lwp (lambda () (let f () (pause) (newline) (f))))
(start)
TODO
阴阳谜题
(define (get-cc) (call/cc (lambda (c) c)))
(define foox (lambda (foo) (display "@") foo))
(define fooy (lambda (foo) (display "*") foo))
(let *((yin (foox (get-cc)))
(yang (fooy (get-cc))))
(yin yang))
可以在纸上推导一下,这里 tricky 的地方在于,每次 yin 的 get-cc 被调用时,会产生一个“新”的环境,这个环境中 yin 的值为嵌套多次的 yang 的 cc ,然后开始反复的 (yangcc-old yangcc-new)
直到 yangcc-old
转变为 yincc
开始新的一轮循环。
TODO: 这里为什么内存不会溢出,尾递归是如何起作用的?