CPS冥想 - 1 重新审视CPS

这篇文章是在阅读Eric Lippert大神的MSDN Blog文章时同步写成的,其中主要是各种翻译,同时还混杂自己阅读文章的笔记和感想。

原博文地址 http://blogs.msdn.com/b/ericlippert/archive/2010/10/21/continuation-passing-style-revisited-part-one.aspx

CPS是Continuation Passing Style的缩写,关于这种风格,E.L.大神和通常的程序风格做了一个对比:

通常的程序:

  记录下程序当前的状态,比如本地变量的值等,一般保存在栈里面

  把程序的控制权交给“子过程”,直到其返回

  恢复到调用前的状态,此时已经知道了子过程的返回值

而CPS是一种没有“子过程”和“返回”的风格。取而代之的是,当前函数执行的最后一条指令是调用下一个函数,因为没有函数会返回,你也就不需要保存调用之前的值,现在程序在什么地方也没有什么意义,因为程序永远不会回来。

显然从这里看,结合以前的知识,CPS变换也就是所谓的尾递归形式,因为是尾调用,所以前面栈上的东西也就没有存在意义了,于是优化的时候栈空间可以重复利用,也就是所谓的尾递归优化。

为了确保事情按我们想要的顺序发生,调用新的函数的时候给他传一个continuation,continuation本身也是一个函数,它负责执行后面所有的东西。

E.L.大神用C#写的例子,幸好我看得懂:(毕竟人家是C#的爹们之一)

我们有以下四个函数 string M(int); bool B(); int C(); int D();

假定C#没有?:三目运算符,那么该如何实现 M(B() ? C() : D());

忽略废话,我们有

T Conditional<T>(bool b, Func<T> consequence, Func<T> alternative)
{
  if (b) return consequence(); else return alternative();
}

直接调用 M(Conditional(B(), ()=>C(), ()=>D()); 就好

(C#的lambda写起来真是爽)

好了下面要强行把Conditional写成CPS风格的,怎么办,这么分析:

  M调用结束后应该调用一些东西(用来继续控制流),姑且叫它currentContinuation,先不管它到底是什么样

  需要重写B使得B接受一个接受bool的continuation,姑且用Action<bool>代替“一个接受bool的continuation”,现在有了void B(Action<bool>)

  B的continuation,也就是在B之后发生的事情是什么?

    先跳步:形式是 B(b=>M(Conditional(b, ()=>C(), ()=>D()))) ,B接受的lambda接受一个bool,然后把它传给Conditional

  现在B是CPS了,但是传给B的lambda不是CPS的,为了让lambda也变成CPS的,我们重复对B的分析:

    M需要接受一个Action<string>,C和D需要接受Action<int>

    至于Conditional,仍然需要对他的参数有惰性求值的特性,但是调用的那些lambda已经没有返回值了,所以,那个lambda也要接受一个continuation。

  所以有: B(b=>Conditional(b, c=>C(c), c=>D(c), t=>M(t, currentContinuation)))

Conditional现在是这样的:

void Conditional<T> (bool condition, Action<Action<T>> consequence, Action<Action<T>> alternative, Action<T> continuation)
{
  if (condition)
    consequence(continuation) ;
  else
    alternative(continuation);
}

总之:B执行然后把bool结果传给参数lambda,这个lambda用这个bool和其他三个lambda调用Conditional,Conditional决定把第三个lambda传给前两个中的哪个,假设它选择了传给alternative,那么,D执行然后把他的结果传给continuation,也就是t=>M(currentContinuation, t) ,然后M用这个整数t做一些事情,再调用之前说过的currentContinuation,然后就OK了。

没有返回,每个方法都是void的,每个委托(匿名函数,lambda)都是一个Action,函数最后做的事情就是调用其他函数,调用的时候前面的调用栈已经没用了,因此编译器可以优化之前的代码,使得没有任何函数会消耗调用栈的空间,值全都临时存储在寄存器之类的临时位置。

也许你会觉得这TM是什么烂代码?但是你看,我们已经做出了一个CPS版本的?:运算符啊,行为完全与之相同,有用lambda实现的惰性求值,控制流由继续向最后面的continuation传合适的方法得以继续。

然而该用continuation解决的事我们一个都没干。

Continuation是控制流的异化,目前我们只讨论了一个单continuation传来传去的系统,因为continuation只是一个委托(lambda),所以我们没有理由只在系统里面用一个continuation。

下一部分:一些关于更复杂的控制流的冥想。

时间: 2024-11-02 22:18:16

CPS冥想 - 1 重新审视CPS的相关文章

致佳音: 推箱子游戏自动求解算法设计(二)

这一个小节我们说一说传说中的A×算法,其实之前也上传过类似的小件件,这里我们就去剖析一下它 毕竟在游戏程序,我们要从一点移动到另一点,并得到最短路程的轨迹,类似这种算法还有好几种,执行效率都差不多,不过大多不能得到轨迹 首先,从一点移动到另一点,最快就是直接走过去了,就像小男生爱上小女生,最好的办法就是直接走到她面前说:我爱你 不过理想状态,几乎是没有的,弯路那是必然的经过,有曲线,其实更美-- 那么弯路该怎么走呢,是不是先去背景看下毛主席,再去三亚晒个太阳,再回来告诉她外面的世界好美,不,不,

样条曲线catmull rom转bezier

b0,..,b3是贝塞尔,c-1, c2是catmull rom控制点 [b0] = 1 [ 0 6 0 0] [c_1] [b1] - [-1 6 1 0] [c0] [b2] 6 [ 0 1 6 -1] [c1] [b3] [ 0 0 6 0] [c2] Qt版本代码: QList<QPointF> cps; cps.append(QPointF(0, 100)); cps.append(QPointF(75, 75)); cps.append(QPointF(200, 150)); cp

LVS详解及nat类型的实现

1.1 lvs Linux Virtual Server, 章文嵩所开发. lvs又称为layer4 router和layer4 switch,它是根据目标地址和端口作出转发与否的决策,根据调度方法作出转发至哪一个后端的决策. lvs组成部分:ipvs, ipvsadm.ipvs工作在内核,ipvsadm工作在应用层.它们之间的关系就相当于netfilter和iptables.同时,ipvs是工作在netfilter之上的. 1.1.1 ipvs工作方式 netfilter五个链:PREROUT

通过mybatis工具generatorConfig.xml自动生成实体,DAO,映射文件

简介 Mybatis属于半自动ORM,可以利用mybatis工具generatorConfig.xml自动生成DAO.实体.映射文件的方式来代替手动书写的方式,这样既提高了工作效率也可以在项目避免出现的一些细微难调试的BUG. 前提条件: 1.需要准备的第三方jar包为: mybatis-generator-core-1.3.2.jar和mysql-connector-java-5.1.39-bin.jar, 其中mybatis-generator-core-1.3.2.jar的下载地址为: h

20170428 关于headers和user_agent的文章自用

截止今天,关于精准广告定向技术的介绍已经全部写完.介绍的写作初衷是总结自己的知识,将知识从片段的.隐形的转化为可以向别人讲述.能够给人帮助的.在总结的过程中自己也提升了很多,同时希望这些内容能够切实的给刚进入这个行业的同学们以帮助. 一.基础知识篇: Http Header之User-Agent User Agent中文名为用户代理,是Http协议中的一部分,属于头域的组成部分,User Agent也简称UA.它是一个特殊字符串头,是一种向访问网站提供你所使用的浏览器类型及版本.操作系统及版本.

Spark On YARN内存分配

本文转自:http://blog.javachen.com/2015/06/09/memory-in-spark-on-yarn.html?utm_source=tuicool 此文解决了Spark yarn-cluster模式运行时,内存不足的问题. Spark yarn-cluster模式运行时,注意yarn.app.mapreduce.am.resource.mb的设置.默认为1G Spark On YARN内存分配 本文主要了解Spark On YARN部署模式下的内存分配情况,因为没有

js 循环获取值时-缓存数组(或集合)的长度

$scope.footActiveIndex = 1;    $scope.noData = false;    var iPageSize =10,        needRequest = true,//是否需要刷地址选择//是否要刷新loaddata        newCityPickerQu = [],//根据定位确定当前地址选择范围        num1,num2;//附近 $scope.info = {        area : '',        city : '',  

互联网精准广告定向技术: 一切你该了解的知识总结与整理

转于:http://www.damndigital.com/archives/58893 第一方Cookie和第三方Cookie 大多数的第三方监测工具和网站分析工具都会采用第三方Cookie.所谓第一方和第三方的说法,是用来确定Cookie的归属的,这个归属是指 Cookie中记录的域(domain).第一方和第三方的唯一区别只是:Cookie中的域名是否和被访问网站的域一样,是就是第一方,否就是第三方. 举个例子:如果你访问网站www.chinawebanalytics.cn的时候,网站在你

sslscan

msf > use auxiliary/pro/web_ssl_scan msf auxiliary(web_ssl_scan) > show options Module options (auxiliary/pro/web_ssl_scan): Name          Current Setting  Required  Description ----          ---------------  --------  ----------- REPORT_WEAK   true