Continuation是由Scheme引入函数式编程中的。这个概念相当重要,但是理解起来却比较困难——也许对于我来说困难,但对于别人来说并不一定是这样。说它重要,是因为应用面比较广,比如协作例程、异常处理;还可以模拟别的编程语言里的return、实现无条件跳转等。其最大,也是最广泛的应用之一就是用于web服务器的开发。
在对任意一个Scheme表达式求值时,必须记录两件事:
- 对什么求值
- 对求出的值做什么
第二件事便是continuation。
这个定义相当简单。例如:
对于表达式(if (null? x) (quote ()) (cdr x))有六个continuations,分别等待如下值:
- (if (null? x) (quote ()) (cdr x))的值
- (null? x)的值
- null?的值
- x的值
- cdr的值
- x的值
其中,(cdr x)的值没有列出来是因为她就是第一个continuation等待的值。从这里可以看出,continuation是一个计算环境,它在等待一些计算,只有这些计算完成后,才能进行该计算环境相关的计算。也就是说,continuation是一个函数。
Scheme允许用call-with-current-continuation函数(间写为call/cc)对任意表达式的continuation进行捕捉。call/cc必须以一个函数p为参数,而函数p又必须只有一个参数k,这个参数k就是被捕获的continuation,也是一个函数。call/cc构造当前的continuation并传递给p。每当k被应用于一个值,它就返回call/cc的continuation的值,而这个值最终就是应用call/cc的值——或者说是(call/cc p)的值。例如:
- (call/cc
- (lambda (k)
- (* 5 4)))
- (call/cc
- (lambda (k)
- (* 5 (k 4))))
- (+ 2
- (call/cc
- (lambda (k)
- (* 5 (k 4)))))
第一个例子中,call/cc捕获的continuation就是返回一个值,并将其传递给匿名函数。这个匿名函数并没有使用k,因此,其结果为20。第二个例子中的continuation与第一个相同,只不过在匿名函数中将continuation应用于4,因此结果为4。第三个例子中的continuation是对其等待的值进行+2操作,因此,结果为6。
上有个例子:
- (define the-continuation #f)
- (define (test)
- (let ((i 0))
- ; call/cc calls its first function argument, passing
- ; a continuation variable representing this point in
- ; the program as the argument to that function.
- ;
- ; In this case, the function argument assigns that
- ; continuation to the variable the-continuation.
- ;
- (call/cc (lambda (k) (set! the-continuation k)))
- ;
- ; The next time the-continuation is called, we start here.
- (set! i (+ i 1))
- i))
下面再来看两个比较复杂一点的例子。
例1:
- (let ([x (call/cc
- (lambda (k) k))])
- (x (lambda (ignore) "hi")))
例2:
- (((call/cc
- (lambda (k) k))
- (lambda (x) x))
- "HEY!")
call/cc捕获当前的continuation,因为(lambda (k) k),所以当前的continuation被返回并直接应用于(lambda (x) x),此时continuation就变为对(lambda (x) x)求值,然后应用到(lambda (x) x),最后将结果应用到"HEY!",即((f f) "HEY!")。
下面这个例子就比较有意思了:
- (define retry #f)
- (define factorial
- (lambda (x)
- (if (= x 0)
- (call/cc
- (lambda (k)
- (set! retry k)
- 1))
- (* x (factorial (- x 1))))))
- (* 4 (factorial 3))
- (* 4 (* 3 (factorial 2)))
- (* 4 (* 3 (* 2 (factorial 1))))
- (* 4 (* 3 (* 2 (* 1 (factorial 0)))))
- (* 4 (* 3 (* 2 (* 1 (call/cc (lambda (k) (set! retry k) 1))))))
前面提到continuation的应用如协作例程、异常处理、模拟别的编程语言里的return、实现无条件跳转,下面就来看看。
return语句:
- (define (f return)
- (return 2)
- 3)
- (display (f (lambda (x) x))) ; displays 3
- (display (call-with-current-continuation f)) ; displays 2
- (define product
- (lambda (ls)
- (call/cc
- (lambda (break)
- (let f ([ls ls])
- (cond
- [(null? ls) 1]
- [(= (car ls) 0) (break 0)]
- [else (* (car ls) (f (cdr ls)))]))))))
异常处理:可以参考。因为里面涉及到了其他的一些Scheme特性,而我还没有搞懂,因此暂时不做描述。
协作例程(coroutines):
- ;;; A naive queue for thread scheduling.
- ;;; It holds a list of continuations "waiting to run".
- (define *queue* '())
- (define (empty-queue?)
- (null? *queue*))
-
- (define (enqueue x)
- (set! *queue* (append *queue* (list x))))
- (define (dequeue)
- (let ((x (car *queue*)))
- (set! *queue* (cdr *queue*))
- x))
- ;;; This starts a new thread running (proc).
- (define (fork proc)
- (call/cc
- (lambda (k)
- (enqueue k)
- (proc))))
- ;;; This yields the processor to another thread, if there is one.
- (define (yield)
- (call/cc
- (lambda (k)
- (enqueue k)
- ((dequeue)))))
- ;;; This terminates the current thread, or the entire program
- ;;; if there are no other threads left.
- (define (thread-exit)
- (if (empty-queue?)
- (exit)
- ((dequeue))))
- (define (do-stuff-n-print str)
- (lambda ()
- (let loop ((n 0))
- (when (< n 10)
- (display (format "~A ~A\n" str n))
- (yield)
- (loop (+ 1 n))))))
-
- ;;; Create two threads, and start them running.
- (fork (do-stuff-n-print "This is AAA"))
- (fork (do-stuff-n-print "Hello from BBB"))
- (thread-exit)
这个例子可以简化为:
- (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)
- 取出函数(lambda () (let f () (pause) (display "h") (f)))
- 将f绑定为不接受参数的函数,该函数依次执行(pause)、(display "h")和(f)——无限循环
- 执行(pause),即将含有(display "h")和(f)作为continuation的函数放入列表尾部
- 执行(start)
- 取出函数(lambda () (let f () (pause) (display "e") (f)))
- 将f绑定为不接受参数的函数,该函数依次执行(pause)、(display "e")和(f)——无限循环
- 执行(pause),即将含有(display "e")和(f)作为continuation的函数放入列表尾部
- 执行(start)
- 取出函数(lambda () (let f () (pause) (display "y") (f)))
- 将f绑定为不接受参数的函数,该函数依次执行(pause)、(display "y")和(f)——无限循环
- 执行(pause),即将含有(display "y")和(f)作为continuation的函数放入列表尾部
- 执行(start)
- 取出函数(lambda () (let f () (pause) (display "!") (f)))
- 将f绑定为不接受参数的函数,该函数依次执行(pause)、(display "!")和(f)——无限循环
- 执行(pause),即将含有(display "!")和(f)作为continuation的函数放入列表尾部
- 执行(start)
- 取出函数(lambda () (let f () (pause) (newline) (f)))
- 将f绑定为不接受参数的函数,该函数依次执行(pause)、(newline)和(f)——无限循环
- 执行(pause),即将含有(newline)和(f)作为continuation的函数放入列表尾部
- 执行(start)
- 取出函数(lambda () (k #f)))
- 执行k,即输出"h",调用(f)
- 执行(pause),即将含有(display "h")和(f)作为continuation的函数放入列表尾部
- 执行(start)
- 取出函数(lambda () (k #f)))
- 执行k,即输出"e",调用(f)
- 执行(pause),即将含有(display "e")和(f)作为continuation的函数放入列表尾部
- 执行(start)
- 取出函数(lambda () (k #f)))
- 执行k,即输出"y",调用(f)
- 执行(pause),即将含有(display "y")和(f)作为continuation的函数放入列表尾部
- 执行(start)
- 取出函数(lambda () (k #f)))
- 执行k,即输出"!",调用(f)
- 执行(pause),即将含有(display "!")和(f)作为continuation的函数放入列表尾部
- 取出函数(lambda () (k #f)))
- 执行k,即输出换行符,调用(f)
- 执行(pause),即将含有(newline)和(f)作为continuation的函数放入列表尾部
- 回到20
最后,留下一个问题供大家讨论吧——我目前不知道怎么来解释这个问题。这就是著名的阴阳谜题:
- (let* ((yin
- ((lambda (cc)
- (display #\@) cc)
- (call/cc
- (lambda (c) c))))
- (yang
- ((lambda (cc)
- (display #\*) cc)
- (call/cc
- (lambda (c) c)))))
- (yin yang))
- @*@**@***@****@*****@******@*******@********@*********@**********@***********@************@*************@**************@***************@****************@*****************@******************@*******************@********************@*********************@**********************@***********************@************************@*************************@**************************@***************************@****************************@*****************************@******************************@*******************************@********************************@*********************************@**********************************@***********************************@************************************@*************************************@**************************************@***************************************@****************************************@*****************************************@******************************************@*******************************************@********************************************@*********************************************@*********************
PS. 由于我自己并没有太理解continuation,因此,这篇就更像一个信息的翻译和聚合,并没有太多自己的理解在里面。写完这篇,我对于continuation依然还是懵懵懂懂的,更谈不上运用它。也许随着时间的流逝,随着对Scheme的熟悉,会逐渐熟悉continuation吧。
参考