http://www.ps.uni-sb.de/~duchier/python/continuations.html
A continuation is a procedure that takes the value of the current expression and computes the rest of the computation.
Continuation是一种非常古老的程序结构,关于它的理论分析可谓渊源流长,参见
http://library.readscheme.org/page6.html
continuation简单的说起来就是entire default future of a computation, 即对程序"接下来要做的事情"所进行的一种建模. 这一概念在理论上当然存在着巨大的价值, 至少它使得我们有一种手段把程序未来的运行过程明确的表达出来(给它取了个名字), 从而有可能对之作进一步的分析. continuation是对未来的完整描述, 这对于理论分析而言是有很多方便之处的, 正如统计学中最常见的分析工具是分布函数而不是密度函数一样. 实际上任何程序都可以通过所谓的CPS(Continuation Passing Style)变换而转换为使用continuation结构, 例如
int foo(int x){
return x+1;
}
==>
void foo(int x,Continuation c){
c.continueWith(x+1);
}
使用continuation的函数不"返回"值,而是把值作为一个参数传递给continuation从而"继续"处理值. 在传统的软件理论中, 程序本身在运行期是固定不变的, 我们只需要记录下执行点(excution point)的信息(例如指针位置和堆栈内容)即足以完整的描述程序未来的运行情况, 因此continuation有时也被看作是"带参数的goto", 是goto语句的一种函数形式.
在函数式语言中, continuation的引入是非常自然的过程, 考察如下函数调用
h(g(k(arg)))
根据函数的结合律, 我们可以定义复合函数 m = h(g(.)), 它自然的成为 k(arg)的continuation. 在理论上我们有可能利用泛函分析的一些技术实现对于continuation(复合函数)的化简, 但实践已经证明这是极为艰难的, 主要是我们的程序不可避免的要涉及到程序与数据的纠缠.
在引入continuation概念之后, 程序运行的表述是非常简单的:
continuation.proceed();
针对串行程序,我们可以建立更加精细的运行模型。
while(continuation.hasNextStep())
continuation.proceedOneStep();
只要以某种方式构造出一种continuation closure(这意味着我们能够通过单一变量来表示程序未来的运行结构), 我们就有可能在某个层面上以如上方式实现对程序的一种简洁的描述.
如果我们的眼界开阔一些, 不拘泥于构造语言级别通用的continuation结构(这需要以抽象的方式定义并保存任意程序的完整运行状态), 而是考察"对程序未来运行的整体结构进行建模"这一更宽广的命题, 我们很快就能发现大量对于continuation概念的应用. 例如实现AOP(Aspect Oriented Programming)的interceptor时所经常使用的MethodInvocation对象.
class MyInterceptor implements MethodInterceptor{
public Object invoke(MethodInvocation invocation){
doSomeThingBeforeRawMethodCall();
return invocation.proceed();
}
}
在网络编程中, 一种常用的设计模式是Observer模式, 即注册监听器(listener)来处理接收到的网络指令. 在一些比较复杂的网络协议中, 网络指令之间往往存在一定的关联, 我们可以通过建立一个庞大的有限自动机来描述所有指令之间的关联规则, 也可以采用如下方式动态的实现对于监听器的调整.
class ACommandListener{
public void onEvent(Event event, FutureListeners futureListeners){
handleEvent(event);
futureListeners.clear();
futureListeners.add("BCommand", new BCommandListener());
futureListeners.add("CCommand", new CCommandListener());
}
}
这种方式可以看作是对程序未来运行结构的一种动态调整. 实际上沿着这种方式深入下去, 我们甚至可以建立一种完整的动态工作流(workflow)机制.
最近struts和webwork步cocoon和rife的后尘, 相继引入了对web continuation的支持, 在后台程序中实现了对于page flow的完整描述, 这无疑是一些非常有趣的工作. 例如现在我们可以编写
void onRequest(){
funcA();
Input input = sendPageAndWait("collectionInfoFromUser.jsp");
handleInput(input);
}
在调用sendPageAndWait的时候, web框架会保存当前函数调用的continuation, 向用户返回页面collectionInfoFromUser.jsp, 等待用户提交表单之后, web框架重新激活我们所保存的continuation, 继续执行我们的函数. 这种做法与系统调用和线程调度等机制是非常类似的.
有些人认为这种基于continuation的方式可以自然的解决在session中保存并清理变量的问题, 这显然是一种大材小用的做法, 而且事实上使用一种通用的continuation 实现很有可能在无意中保存了过多的临时变量, 从而对系统性能造成极大的损害. 有趣的是, 在Mach3.0中对系统线程所作的一项改进即称为continuation, 其动因恰在于避免保留线程堆栈,希望使用完全无状态的continuation函数.(参见Uresh Vahalia的经典著作"UNIX Internals"
http://www.china-pub.com/computers/common/info.asp?id=12731).
在传统的系统调用实现中
syscall_l(argl)
{
...
thread_block();
f2(arg);
return;
}
f2(arg){
...
return;
}
thread_block()函数会阻塞住当前系统调用过程, 并自动保存所有堆栈变量, 等待内核重新把控制权返回给调用函数. 在使用continuation函数的方式中, 我们需要显式的存取状态变量,
syscall_1(arg1)
{
...
save arg1 and any other state information;
thread_block(f2); // thread_block(void * (contiuationFunc));
/* not reached */
}
f2()
{
restore argl and any other state information;
...
thread_syscall_return(status);
}
在这种方式中thread_block()并不返回到调用者。线程恢复执行时,内核把控制权传递给f2(). 函数thread_syscall_return()用来从系统调用返回到用户级。"整个过程对用户是透明的,用户所看到的只是从系统调用一个同步返回 ". 在Linux系统内核中所使用的bottom_half机制也是基于类似的原理.