Continuation Pass Style
在函数式编程(FP)中有一种被称为Continuation Passing Style(CPS)的风格。在这种风格的背后所蕴含的思想就是将处理中可变的一部分抽象为一个function,并将其作为一个参数传入。这是高度抽象的方法,所带来的表达的威力也是无与伦比的。
下面举一个例子,实现CPS描述的fold函数。
在此之前需要简单介绍一下fold概念,在FP中这是一个非常有效的处理list的工具。一般它的signature是fold(f,initial,list)。它所对应的效果是:f(e0,f(e1,….f(en,initial)…),e0到en也就是list中的所有元素,当我们取f为加法操作的时候就可以计算得到所有元素的和。(Note:这里我提到的fold是右结合的,考虑到有时候f没有结合律,所以事实上还存在一个左结合的fold)
下面正式引入CPS风格的fold:
cfold(f,z,list) = f(e0,z, lambda y.cfold(f,y,er))
其中的e0为list第一个元素,er为除去了e0以外元素构成的list,lambda y:cfold(f,y,er)表示一个匿名的函数,它接受一个参数,递归的执行cfold。整个函数的终止条件是list为空的时候。
cfold和fold不同的地方就在于第一个参数f上,cfold的f接受三个参数,这三个参数的意义可以从cfold的定义式中推敲。
看上去cfold比fold复杂了不少,但是由此其表达能力有了进一步的提升,下面两个即由cfold定义的fold:
fold1(f,z,list) = cfold( lambda x g t.f(x,t(g)), z, list )
fold2(f,z,list) = cfold( lambda x g t.t(f(x,g)), z, list )
fold1的功能和fold完全相同,fold2则可以逆序的执行运算f(en,f(e[n-1],….f(e0,initial)…)
这就是CPS的强大之处,事实上这也正是数学中函数的强大表达能力的体现。
Reference : Yet Another Haskell Tutorial Section 4.6
Continuation is the calculation what should be performed before returning to the toplevel. Actually, the continuation exists everywhere during computation. For instance, in the case of (* 3 (+ 1 2)),
the calculation that should be done is multiplying 3 { (* 3 []) }
after evaluating (+ 1 2). As most of languages do not treat it explicitly, however, it is unfamiliar ot many programmers.
3. Continuation Passing Style (CPS)
3.1. Simple CPS
CPS is a programming style in which the successive function(s) that uses the result of current function is given as an argument of the current function. [code 1] shows adding and multiplying written in CPS. In k+ and k*, k is the successive function.
[code 1]
(define (return x)
x) (define (k+ a b k)
(k (+ a b))) (define (k* a b k)
(k (* a b)))
[example 1] shows how to calculate (* 3 (+ 1 2)) using the CPS.
[example 1]
(k+ 1 2 (lambda (x) (k* x 3 return)))
In the ordinary form of Scheme, values that are calculated in parentheses go outside of them. In the CPS, on the contrary, values go inside of other parentheses. In [example 1], k+ passes the value of (+ 1 2) to (lambda (x) (k* x 3 return)) and k* passes the result of (* (+ 1 2) 3) to return.
在一般scheme代码中,在圆括号内被计算的值到外面使用,在cps恰好相反,值又到了另一个圆括号内。k+函数传递(+ 1 2)到lambda函数中。
例子;
无限循环:
(let ((start #f))
(if (not start)
(call/cc (lambda (cc)
(set! start cc))) empty) (display )
(display )
(start)) ;start现在是一个存储过程
12121212........
Continuation(First-class continuation)是控制指令执行顺序的一种能力。可以用来从当前执行函数跳转回上层调用函数,或者跳转到之前以退出的函数。可以把它想象成将程序的当前状态保存下来,以便以后恢复(有点像给VM做snapshot)。但要注意的是,真正的Continuation仅仅存储执行上下文,而非程序的数据。下面是的比喻能够很好的解释这一点:
话说你在厨房的冰箱前,考虑来点三明治尝尝。如果这时候你做了个continuation,并将其存放到你的口袋中。然后你把火鸡和面包从冰箱里取出来拿到餐台,并把他们做成了你想要的三明治. 此时,如果你把你的continuation从口袋中取出来,并且调用一次的话。你会发现你突然又身处冰箱前, 考虑来点三明治尝尝. 不过,幸运的是, 此时餐台上已经有了个你想要的三明治。而火鸡和面包都不见了. 那么,你就可以径直去把它吃掉了。 :-) (有点像月光宝盒吧?)
Scheme 通过"catch" 以及 call/cc支持Continuation。Common Lisp虽然在标准中没有支持Continuation,但是通过cl-cont可以使用Delimited Continuation。(支持closure以及proper tail recursion的语言都可以实现continuation-passing style,从而实现Continuation。)
Continuation可以用来实现一些常用的设计模式,比如Coroutines (又叫做Green Thread),Exception handling等。以下给出用Scheme编写的演示代码:
(define the-continuation #f) (define (test)
(let ((i ))
; 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 ))
i))
Defines a function test
that sets the-continuation
to the future execution state of itself:设置the-contiutaiton为未来的执行状态。
> (test)
1
> (the-continuation)
2
> (the-continuation)
3
> ; stores the current continuation (which will print 4 next) away
> (define another-continuation the-continuation)
> (test) ; resets the-continuation
1
> (the-continuation)
2
> (another-continuation) ; uses the previously stored continuation
4
For a gentler introduction to this mechanism, see call-with-current-continuation.
协程:
Coroutines
This example shows a possible usage of continuations to implement coroutines as separate threads
#lang racket
;;; 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)))) ;;; The body of some typical Scheme thread that does stuff:
(define (do-stuff-n-print str)
(lambda ()
(let loop ((n ))
(display str )(display n) (newline) (yield) ;每输出一次str,就将运行权交给其他线程
(loop (+ n))))) ;;; 创建并运行两个线程.
(fork (do-stuff-n-print "This is AAA"))
(fork (do-stuff-n-print "Hello from BBB"))
(thread-exit)
以下是程序执行结果:
This is AAA 0
Hello from BBB 0
This is AAA 1
Hello from BBB 1
This is AAA 2
Hello from BBB 2
...
转自wikipedia:http://en.wikipedia.org/wiki/Continuation
http://www.shido.info/lisp/scheme_cc_e.html
http://blog.csdn.net/cnnzp/article/details/7832106
http://www.cs.utah.edu/~mflatt/past-courses/cs6520/public_html/s02/cps.pdf
http://en.wikipedia.org/wiki/Continuation-passing_style
https://cgi.soic.indiana.edu/~c311/doku.php?id=assignment-6
http://www.ibm.com/developerworks/cn/linux/l-advflow.html
http://martinfowler.com/bliki/Lambda.html
http://www.zhihu.com/question/20259086
非常好的解释:
http://www.ibm.com/developerworks/cn/linux/l-schm/part3/
Scheme语言相对Lisp语言的重要特征是提出并实现了continuation,这是让初学者最难理解,也是最让程序员心动的特征。那么Continuation到底是什么呢?在运算Scheme表达式过程中,我们必须跟踪两个东西:1、运算什么?2、什么与值相关?看一看下面表达式的计算: (if (null? x) (quote ()) (car x)) 。其中首先要运算的是(null? x),然后在这个值的基础上再运算(quote ())或(cdr x),要运算的是(null? x)而与值相关的是(quote ())和(car x);这里把与值相关的称为运算的continuation。我们看(null? x)表达式中与值相关的东西就有三个,1、表达式本身,结果为#t或#f;2、过程null?,它是一个lambda表达式;3、变量x,它应用当有一个值。那么上面的表达式里面有几个continuation呢?答案是六个,上面的表达式本身,我们说过的三个,car也是一个lambda表达式,还有它后面的x,也和值相关;而(car x)没有计算在内,因为它与上面表达式结果之一是相同的。
在任何表达式中,Continuation可以用一个叫call-with-current-continuation的过程来得到,多数情况下它被简化为call/cc。在guile的1.6.4或以前版本中,简化代码如下所示:
(define call/cc call-with-current-continuation)
而在其它的Scheme语言的实现版本中完全可以不用这样定义,而直接使用call/cc。
call/cc过程的唯一个参数应该是一个过程(或lambda表达式),而且这个过程只能有一个参数,continuation就绑定在这个参数上。看下面的代码:
guile> (define call/cc call-with-current-continuation)
guile> call/cc
#<procedure call-with-current-continuation (proc)>
guile> (call/cc (lambda (k) 5))
5
;;;此时过程参数k未用到,所以取过程的返回值5做为结果
guile> (call/cc (lambda (k) (* 5 (k 8))))
8
;;;此时过程参数k绑定为8,所以其结果为8
guile> (+ 2 (call/cc (lambda (k) (* 5 (k 8)))))
10
;;;此时结果在我们意料之中了 (个人注:想看看call/cc 直接调用中的参数到底是什么?
(define global 1)
(call/cc (lambda (k) (set! global k)( * 5 (k 8))))
(procedure? global) #t
(global (lambda (x) x))
#<procedure>
> (global 5)
5
)
可以利用call/cc这一特点,让它从一个循环中跳出来,这有点像C语言中的break,看下面的代码:
guile> (call/cc (lambda (return)
(for-each (lambda (x) (if (< x 0) (return x)))
'(99 88 77 66 55))
#t))
#t
其结果为#t,因为for-each运算过程中未出现过(< x 0)的情况,所以(lambda (return ) ...)的结果为#t,call/cc取此值为最终结果。
guile> (call/cc (lambda (return)
(for-each (lambda (x) (if (< x 0) (return x)))
'(11 22 33 44 -55 66 77))
#t))
-55
其结果为-55,因为当遇到小于0的数时,return就绑定x,call/cc就返回此值,即从for-each处理过程中跳出来,这是call/cc的重要功能之一,也是最基本的功能。
call/cc 还可以这样操作:
guile> (define foo #f)
guile> (call/cc (lambda (bar) (set! foo bar) 123))
123
guile> (foo 456)
456
guile> (foo 'abc)
abc
上面的操作中,call/cc绑定了lambda表达式的参数bar,而后我们又设定foo的值为bar,此时foo即为一个灵活的continuation;最后我们又让lambda表达式的值为123,所以整个call/cc表达式的值为123,如果我们不加这个123而直接结束这个表达式的话,此表达式就没有结果,不返回任何值。
当foo成为一个continuation后,我们可以象调用过程那样来调用它(当然它并不是过程),其后面绑定的值即为此continuation的结果,如上面的代码运行所示。
这段代码的前提是有call/cc的定义,如果你直接运行guile后就输入上面的代码是肯定会出错的。
http://c2.com/cgi/wiki?CallWithCurrentContinuation
非常的文章:
http://article.yeeyan.org/view/213582/179432
http://matt.might.net/articles/by-example-continuation-passing-style/