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/

http://matt.might.net/articles/programming-with-continuations--exceptions-backtracking-search-threads-generators-coroutines/

http://daweibalong.iteye.com/blog/1882795

scheme Continuation的更多相关文章

  1. 延续(continuation)

    首先看下延续的定义: 续延是在运行中被暂停了的程序:即含有计算状态的单个函数型对象.当这个对象被求值时,就会在它上次停下来的地方重新启动之前保存下来的计算 在计算机科学和程序设计领域,延续是计算机程序 ...

  2. 理解 Continuation

    理解 Continuation (2012-08-26 10:39:34)     终于,我也不能免俗地要来谈谈这几个 Schemer 的必谈话题(顺便山寨了一个标题). Scheme 是一门神奇的编 ...

  3. [Scheme]Understanding the Yin-Yang Puzzle

    这题目确实比较杀脑细胞... 原题: (let* ((yin ((lambda (cc) (display "@") cc) (call-with-current-continua ...

  4. 与Scheme共舞

    发表在<程序猿>2007年7月刊上.不log上写帖子不用考虑版面限制,所以这里的帖子比发表的啰嗦点.赵健平编辑,Jacky,和刘未鹏都给了我非常多帮助,在这里一并谢了.免费的Scheme实 ...

  5. scheme 阴阳谜题

    本篇分析continuation的一个著名例子"阴阳迷题",这是由David Madore先生提出的,原谜题如下: (let* ((yin ((lambda (foo) (disp ...

  6. Teach Yourself Scheme in Fixnum Days 13 Jump跳转

    Jumps One of the signal features of Scheme is its support for jumps or nonlocal control. Specificall ...

  7. call/cc 总结 | Scheme

    call/cc 总结 | Scheme 来源 https://www.sczyh30.com/posts/Functional-Programming/call-with-current-contin ...

  8. 开始学习Scheme

    开始学习Scheme   函数式编程(Functional Programming)是在MIT研究人工智能(Artificial Intelligence)时发明的,其编程语言为Lisp.确切地说,L ...

  9. scheme 之门

    scheme 之门 开始之前 这是一篇 Scheme 的介绍文章. Scheme 是一个 LISP 的方言, 相对于 Common LISP 或其他方言, 它更强调理论的完整和优美, 而不那么强调实用 ...

随机推荐

  1. error C2143 & error C4430

    错误 1 error C2143: 语法错误 : 缺少“;”(在“*”的前面) 错误 2 error C4430: 缺少类型说明符 - 假定为 int.注意: C++ 不支持默认 int 错误 3 e ...

  2. phpcms 内部检测用户名长度更改

    <?php /*1.js部分ajax就不说了,很好改...把min  max更改下就行了具体在三个地方更改: inputValidator({min:2,max:40, //默认是20 1.会员 ...

  3. pyqt 右击+指定位置点击例子学习

    # -*- coding: utf-8 -*- # python:2.x __author__ = 'Administrator' import sys from PyQt4.QtCore impor ...

  4. easyUI的combobox设置隐藏和显示

    今天遇到一个需求,需要在combobox选择不同选项时,分别切换另一个控件为text或者combobox. 当时想了各种办法,想将combobx和text切换隐藏,但是都没得到自己想要的效果.最终还是 ...

  5. According to TLD or attribute directive in tag file, attribute value does not accept any expressions

    1.错误描写叙述 2014-7-13 17:27:21 org.apache.jasper.compiler.TldLocationsCache tldScanJar 信息: At least one ...

  6. [ES7] Object.observe + Microtasks

    ES6: If you know about the Javascirpt's event loop. You know that any asyns opreations will be throw ...

  7. 初识_IOS-简易计算器-问题总结

    作为一个新手,只好拿所有开发者都写的不想写又没有太大难度的计算器来下手咯.比较细一点,耐心哟. 我们都知道,计算器首要任务就是计算,那我们就直接看成A+B=C,来进行分析了.对A,B,C三个对象进行分 ...

  8. c#中传递参数前加out

    首先:两者都是按地址传递的,使用后都将改变原来参数的数值. 其次: rel 可以把参数的数值传递进函数,但是 out 是要把参数清空,就是说你无法把一个数值 从 out 传递进去的, out 进去后, ...

  9. iOS开发 ~应用程序设计理念:视图控制器(UIViewController)、视图(UIView)

    应用程序设计理念:视图控制器(UIViewController).视图(UIView) 利用视图控制器(底层)管理视图(外观),一对一 1.视图的作用:负责显示的外观 2.视图控制器的作用:创建界面. ...

  10. UIView的常用方法

    bringSubviewToFront: 把指定的子视图移动到顶层 - (void)bringSubviewToFront:(UIView *)view 参数 view 需要移到顶层的视图 conve ...