C#-continuation-passing style(CPS)

如果你还不是很了解CPS是什么,那么推荐几个链接给你(希望你的英语要给力啊):

http://blogs.msdn.com/b/wesdyer/archive/2007/12/22/continuation-passing-style.aspx

http://en.wikipedia.org/wiki/Continuation-passing_style

http://blogs.msdn.com/b/ericlippert/archive/2010/10/22/continuation-passing-style-revisited-part-two-handwaving-about-control-flow.aspx

CPS(continuation-passing
style):字面可以理解为后继式传递格式,这是在函数式编程中的一个特性。但在C#中Lambda表达式和Action<T>
泛型委托结合起来,能够很好地实现这一特性。

原来我们用结构化处理异常的方式:

void Q()
{
try
{
B(A());
}
catch
{

C();
}
D();
}

int A(int a)
{
throw;
return 0; // 不可达,暂时忽略

}
void B(int x) { //to do something }
void C() { //to do something
}
void D() { //to do something }

这些方法调用是否面向对象,这不是我们要讨论的话题。但是不管怎么说,try{...}catch{...}finally{...}这种结构化的异常处理方式,至今还是在被广泛使用。

结构化的一个特点,耦合性强,关联度高。就像流水线一样紧密结合。

让我们先看一下,这个调用流程:

首先执行方法A,如果A的调用未产生任何异常,则返回结果给B作为参数,调用B方法,如果方法B执行正常。则方法C不会被执行,直接跳到方法D开始执行。

如果方法A或B任何一方产生异常,执行将会被中止。调用将会跳转到方法C执行(当然前提是,该异常能被顺利捕获到)。最后再调用方法D。

其实CLR在采用结构化的异常处理机制时,实现了一些帅选器和处理器等内部和语言机制,可参考《CLR Via C#
3.0》。但是,既然我们说这种异常处理方式是一种环环相扣的,类似于流式的,为什么我们不能模拟采用CPS来实现呢?

对于ABCD四个方法,我们都考虑两种情况(其实就是一种if ...else....结构)一种情况:方法调用成功;一种情况方法调用失败。

于是,可以这样定义:

Action<T>:接受一个类型为T的参数,并且没有返回值;

void A(Action<int> normal, Action error);

void B(int x, Action normal, Action error) { whatever }
void C(Action
normal, Action error) { whatever }
void D(Action normal, Action error) {
whatever }

注:所有的normal,都可以想象为,我们通常不考虑异常的方法体,所有的error都可以认为对原方法体中出现异常的处理方法

这样对try块的处理逻辑抽象为:

Try (
/* tryBody */ (bodyNormal,
bodyError)=>A(
/* normal for A */ x=>B(x, bodyNormal,
bodyError),
/* error for A */ bodyError),
/* catchBody
*/ C,
/* outerNormal */ ()=>D(qNormal, qError),
/*
outerError */ qError );
首先,从外部来看try块只能有两个出口:

由try——>outerNormal,将执行:()=>D(qNormal, qError)
用outerNormal

try——>catchBody 将执行:()=>C(outerNormal,outerError)即为::()=>C(()=>D(qNormal,
qError),outerError)

而对于try体,则有:(bodyNormal, bodyError)=>A(x=>B(x,
bodyNormal, bodyError), bodyError);

因此,展开就为:

A(
x=>B( // A's normal continuation
x, // B's argument
()=>D( // B's normal
continuation
qNormal, // D's normal continuation
qError), // D's error continuation
()=>C( // B's
error continuation
()=>D( // C's normal
continuation
qNormal, // D's normal continuation
qError), // D's error continuation
qError)), // C's
error continuation
()=>C( // A's error continuation
()=>D( // C's normal continuation
qNormal, // D's normal continuation
qError), // D's error continuation
qError)) // C's error continuation

如果C抛出异常则,continuation立刻被转入qError执行;

从可读性上来讲这当然不是一种非常好的实现方式。但这是一种思路,我们在编写一些前后关联性很强的方法调用时,并且方法调用不是很多时,可以采用这种做法。

下面的实现很好地体现了CPS的流式调用(伪递归)和数据处理控制权的交接:

实现Factorial:

static void Main()
{
    Factorial(5, x => Console.WriteLine(x));
}

static void Factorial(int n, Action<int> k)
{
    if (n == 0)
        k(1);
    else
        Factorial(n - 1, x => k(n * x));
}
当然CPS还有一个很有用的特性就是能够支持异步回调,在异步编程中很有用。





原文发布时间为:2011-02-24

本文作者:vinoYang

本文来自合作伙伴CSDN博客,了解相关信息可以关注CSDN博客。

时间: 2024-09-10 12:46:55

C#-continuation-passing style(CPS)的相关文章

.NET中的异步编程-Continuation passing style以及使用yield实现异“.NET研究”步

传统的异步方式将本来紧凑的代码都分成两部分,不仅仅降低了代码的可读性,还让一些基本的程序构造无法使用,所以大部分开发人员在遇到应该使用异步的地方都忍痛割爱.本来我在本篇文章中想讨论一下.NET世界中已有的几个辅助异步开发的类库,但是经过思考后觉得在这之前介绍一下一些理论知识也许对理解后面的类库以及更新的内容有所帮助.今天我们要讨论的是Continuation Passing Style,简称CPS. CPS 首先,我们看看下面这个方法: 1: public int Add(上海企业网站设计与制作

一起谈.NET技术,.NET中的异步编程-Continuation passing style以及使用yield实现异步

传统的异步方式将本来紧凑的代码都分成两部分,不仅仅降低了代码的可读性,还让一些基本的程序构造无法使用,所以大部分开发人员在遇到应该使用异步的地方都忍痛割爱.本来我在本篇文章中想讨论一下.NET世界中已有的几个辅助异步开发的类库,但是经过思考后觉得在这之前介绍一下一些理论知识也许对理解后面的类库以及更新的内容有所帮助.今天我们要讨论的是Continuation Passing Style,简称CPS. CPS 首先,我们看看下面这个方法: 1: public int Add(int a, int

.NET中的异步编程-Continuation passing style以及使用yield实现异步

传统的异步方式将本来紧凑的代码都分成两部分,不仅仅降低了代码的可读性,还让一些基本的程序构造无法使用,所以大部分开发人员在遇到应该使用异步的地方都忍痛割爱.本来我在本篇文章中想讨论一下.NET世界中已有的几个辅助异步开发的类库,但是经过思考后觉得在这之前介绍一下一些理论知识也许对理解后面的类库以及更新的内容有所帮助.今天我们要讨论的是Continuation Passing Style,简称CPS. CPS 首先,我们看看下面这个方法: 1: public int Add(int a, int

Continuation Passing C 0.1.1发布 并发系统的编程语言

Continuation Passing C 简称CPC,是一款编写并发系统的编程语言.CPC程序是由CPC翻译器进行处理,从而产生高效的事件循环代码.这种方法提供了最好的两个领域:方便线程相关的编程,和低http://www.aliyun.com/zixun/aggregation/17969.html">内存占用的事件循环代码.当前CPC的实现可以用于编写Hekate.BitTorrent seeder.处理数以百万计的同步种子和同步连接. Continuation Passing C

轻量函数式 JavaScript:九、递归

在下一页,我们将进入递归的话题. (本页的剩余部分故意被留作空白)                       让我们来谈谈递归.在深入之前,参见前一页来了解其正式的定义. 很弱的玩笑,我知道.:) 递归是那些大多数开发者都承认其非常强大,但同时也不喜欢使用的技术之一.在这种意义上,我将之与正则表达式归为同一范畴.强大,但令人糊涂,因此看起来 不值得花那么多力气. 我是一个递归的狂热爱好者,而且你也可以是!我在本章的目标就是说服你,递归是一种你应当带入到你 FP 编码方式中的重要工具.当使用得当

如何设计一门编程语言(八) 异步编程和CPS变换

关于这个话题,其实在(六)里面已经讨论了一半了.学过Haskell的都知道,这个世界上很多东西都可以用monad和comonad来把一些复杂的代码给抽象成简单的.一看就懂的形式.他们的区别,就像用js做一个复杂的带着几层循环的动画,直接写出来和用jquery的"回调"写出来的代码一样.前者能看不能用,后者能用不能看.那有没有什么又能用又能看的呢?我目前只能在Haskell.C#和F#里面看到.至于说为什么,当然是因为他们都支持了monad和comonad.只不过C#作为一门不把&quo

探索c#之递归APS和CPS

  累加器传递模式(Accumulator passing style) 尾递归优化在于使堆栈可以不用保存上一次的返回地址/状态值,从而把递归函数当成一个普通的函数调用. 递归实际上是依赖上次的值,去求下次的值. 如果我们能把上次的值保存起来,在下次调用时传入,而不直接引用函数返回的值. 从而使堆栈释放,也就达到了尾递归优化的目的. 下面我们增加了一个acc的参数,它存储上次的值,在下次调用时传入. static int Accumulate(int acc, int n) { if (n ==

柯里化的前生今世(八):尾调用与CPS

关于 在上一篇中,我们介绍了continuation的概念,还介绍了Lisp中威力强大的call/cc,它提供了first-class continuation,最后我们用call/cc实现了python中的generator和yield. call/cc赋予了我们很强的表达能力,Lisp中的异常处理机制也很人性化. 例如,Common Lisp: Condition_system, 由于call/cc可以捕捉到异常处的continuation, 我们就可以手动调用这个continuation,

如何设计一门编程语言(四) 什么是坑(操作模板)

其实我在写这个系列的第三篇文章的时候就已经发现,距离机器越远,也就是抽象越高的概念,坑的数量是越少的.但是这并不是说,距离机器越近的概念就越强大或者说越接近本质.这是广大的程序员对计算理论的一种误解.大多数人理解编程的知识结构的时候,都是用还原论来理解的,这个方法其实并没有错.但问题在于,"还原"的方法并不是唯一的.很多人觉得,反正你多高级的语言编译完了无非都是机器码嘛.但是还有另一种解释,你无论多低级的语言编译完了无非也就是带CPS变换(continuation passing st