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

上一节说到:我们可以通过跟踪不同的continuation并决定下一步执行哪个来构造任意复杂的控制流。

来看一个比条件跳转更复杂的:考虑最简单版本的try-catch,throw后面不跟表达式,也就是一个goto,goto到最近的catch,代码是这样的:

void Q()
{
  try
  {
    B(A());
  }
  catch
  {
    C();
  }
  D();
} int A()
{
    throw;
    return ; // unreachable, but let's ignore that
}
void B(int x) { whatever }
void C() { whatever }
void D() { whatever }

传统方法理解这段代码,是我们知道这附近有一个catch语句块,保存上下文,调用A,如果正常返回,那么保存上下文然后用A的返回值调用B,如果B正常返回,调用D.如果A或B没有正常返回,寻找之前说过的catch块,然后调用C,调用D。

E.L.大神说真正的CLR里面throw的实现“疯狂地复杂”,假定一个语言没有内置try-catch,那么我们没办法把一些“疯狂地复杂”的东西实现为库函数,所以,我们能否用一个支持CPS的语言实现一个Try()和Throw()函数呢?

我们可以通过给每个可能throw的函数传两个continuation,一个正常情况,一个错误情况。不妨设A会throw,把所有东西翻译成CPS的,有:

void A(Action<int> normal, Action error)
{
Throw(()=>normal(), error);
}

这样就明了了,A干了什么?调用Throw,然后把0传给了它的normal continuation,这个Throw的continuation再调用A的continuation,也就是normal(0)。(我们这里仅仅把Throw看成一个函数调用)

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

所以Throw的实现是什么呢?很简单,Throw调用error continuation然后放弃normal continuation。

void Throw(Action normal, Action error)
{
error()
}

Try的实现比较难,try-catch做了些什么?他为try块创建了一个新的error continuation,但没有给catch创建,这要怎么去做呢,直接给实现

void Try(Action<Action, Action> tryBody,
Action<Action, Action> catchBody,
Action outerNormal,
Action outerError)
{
tryBody(outerNormal, ()=>catchBody(outerNormal, outerError));
}

调用是这样的

void Q(Action qNormal, Action qError)
{
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 );
}

首先,这是CPS的,每个函数都返回void,每个lambda都返回void,每个函数或lambda都在最后调用了其他函数。

它正确吗,我们来脑补debug一遍:

调用Try,Try调用tryBody,tryBody接受两个continuation,Try把outerNormal,也就是一个()=>D(qNormal, qError) ,作为一个normal continuation传给tryBody。
把()=>catchBody(outerNormal, outerError) 作为函数体的error continuation,catch body是C,因此tryBody的参数error continuation就会被求值为()=>C(()=>D(qNormal, qError), qError) 
再来看tryBody,他是一个(bodyNormal, bodyError)=>A(x=>B(x, bodyNormal, bodyError), bodyError) ,我们知道bodyNormal和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

所以调用tryBody就是调用A,A立即调用Throw,传一些复杂的东西作为normal continuation并且把()=>C(()=>D(qNormal, qError), qError) 作为error continuation.

A内部的Throw忽略它的normal Continuation,直接调用()=>C(()=>D(qNormal, qError), qError) 。
那么C干了什么,如果C throw,那么控制流立即转向qError(catch里面又throw,异常处理还要往上走一层)。如果C正常结束,那么执行的是normal continuation,也就是()=>D(qNormal, qError) ,D怎么做就不管它了。
以上假定每个函数都有可能throw,所以每个函数都有一个normal continuation和error continuation。

再回来,如果A不throw会怎样?如果他只是用0去调用它的normal continuation呢,上面写的很清楚,A的normal continuation是去调用B,所以他把0传给B,如你所见,如果B throw,那么控制流传给C,否则传给D。

所以这样就OK了,我们已经把try-catch实现为了函数,而且只用了1行(误),这一点也不复杂嘛(大神原话逃

如你所见CPS就是控制流的异化,continuation是一个代表了即将发生什么的对象。控制流是什么?控制流是决定即将发生什么。任何可以想象的控制流都能用CPS表示。

然和可以想象的控制流真的有很多,这么做可行的原因是任何控制流都是围绕条件goto的,if是goto,循环是goto,子调用时goto,返回是goto,异常是goto,都是goto,有了continuation,任何各种口味的goto以及非本地分支都完全无关紧要,你可以用CPS实现一些十分具有“异国情调”的控制流,可返回的异常,并行求值两个分支的条件表达式,yield return,协程。更Hack的,你可以写一个正着运行过去然后又倒着运行回来的程序。

If it's a kind of control flow, then you can do it with CPS.

下一节:向协程挥手

CPS冥想 - 2 手撸控制流的更多相关文章

  1. 以鶸ice为例,手撸一个解释器(一)明确目标

    代码地址 # HelloWorld.ice print("hello, world") 前言(废话) 其实从开始学习编译原理到现在已经有快半年的时间了,但是其间常常不能坚持看下去龙 ...

  2. php手撸轻量级开发(一)

    聊聊本文内容 之前讲过php简单的内容,但是原生永远是不够看的,这次用框架做一些功能性的事情. 但是公司用自己的框架不能拿出来,用了用一些流行的框架比如tp,larveral之类的感觉太重,CI也不顺 ...

  3. 使用Java Socket手撸一个http服务器

    原文连接:使用Java Socket手撸一个http服务器 作为一个java后端,提供http服务可以说是基本技能之一了,但是你真的了解http协议么?你知道知道如何手撸一个http服务器么?tomc ...

  4. 【手撸一个ORM】MyOrm的使用说明

    [手撸一个ORM]第一步.约定和实体描述 [手撸一个ORM]第二步.封装实体描述和实体属性描述 [手撸一个ORM]第三步.SQL语句构造器和SqlParameter封装 [手撸一个ORM]第四步.Ex ...

  5. 康少带你手撸orm

    orm 什么是orm? 对象关系映射: 一个类映射成一张数据库的表 类的对象映射成数据库中的一条条数据 对象点数据映射成数据库某条记录的某个值 优点:不会写sql语句的程序员也可以很6的操作sql语句 ...

  6. Haskell手撸Softmax回归实现MNIST手写识别

    Haskell手撸Softmax回归实现MNIST手写识别 前言 初学Haskell,看的书是Learn You a Haskell for Great Good, 才刚看到Making Our Ow ...

  7. 手撸基于swoole 的分布式框架 实现分布式调用(20)讲

    最近看的一个swoole的课程,前段时间被邀请的参与的这个课程 比较有特点跟一定的深度,swoole的实战教程一直也不多,结合swoole构建一个新型框架,最后讲解如何实现分布式RPC的调用. 内容听 ...

  8. .NET手撸2048小游戏

    .NET手撸2048小游戏 2048是一款益智小游戏,得益于其规则简单,又和2的倍数有关,因此广为人知,特别是广受程序员的喜爱. 本文将再次使用我自制的"准游戏引擎"FlysEng ...

  9. .NET手撸绘制TypeScript类图——上篇

    .NET手撸绘制TypeScript类图--上篇 近年来随着交互界面的精细化,TypeScript越来越流行,前端的设计也越来复杂,而类图正是用简单的箭头和方块,反映对象与对象之间关系/依赖的好方式. ...

随机推荐

  1. CentOS7下安装Tomcat

    1.下载tomcat. 测试tomcat版本为:apache-tomcat-8.5.6.tar.gz.下载地址:http://tomcat.apache.org/download-80.cgi. 2. ...

  2. Python、PIP环境变量的配置

    Python安装的路径:D:\Python35 pip的环境变量 Python和pip的PATH: PIP下载链接:https://pypi.python.org/pypi/pip 随意解压好,然后C ...

  3. Linux 环境变量配置

    linux 环境,用户通过shell 操作时,系统会为用户初使化环境变量, 比如系统公共资源路径: path , include , bin 等目录. shell 模式下,执行 export , 查看 ...

  4. oracle生成行方法

    数据库记录是行的集合 set of row, 那么如何如何生成集合呢? oracle中常用的是 递归查询(with ... union all ...) 以及 connect by(树形查询) htt ...

  5. [计算机、网络相关历史]unix简史

    本文2001年由台湾“网络农夫”所写,其人生平不祥,此文受鸟哥大力推崇,两人应该相识.文章写得很不错,应该是查了很多资料整理而成的,美中不足的是好多语句不通顺,国考语文绝对不及格,哈哈. 0.我的准备 ...

  6. 使用PHPUnit + Selenium进行自动化测试

    project: blog target: how-to-use-phpunit-selenium-test.md date: 2015-12-22 status: publish tags: - S ...

  7. Steganography-图片隐写术

    今天做DAM的作业,做到图片水印的时候,想起来当初小调同学把言页之庭的种子通过图片发给我.看到下面这个新闻真是觉得碉堡了!!技术宅又一次可以成功而隐晦的表白了!!哈哈哈!! http://war.16 ...

  8. Java甘特图控件swing版免费下载地址

    FlexGantt 控件是现在Java 平台下最先进的甘特图解决方案,使用一个很高的抽象层次,能适用于多种不同的域,例如 ERP 系统.生产计划和日程安排.制造流程系统或项目公文管理程序等.这些使得 ...

  9. 进监狱全攻略之 Mifare1 Card 破解

    补充新闻:程序员黑餐馆系统 给自己饭卡里充钱 ,技术是双刃剑,小心,小心! 前言 从M1卡的验证漏洞被发现到现今,破解设备层出不穷,所以快速傻瓜式一键破解不是本文的重点,年轻司机将从本文中获得如下技能 ...

  10. HDU 5358 尺取法+枚举

    题意:给一个数列,按如下公式求和. 分析:场上做的时候,傻傻以为是线段树,也没想出题者为啥出log2,就是S(i,j) 的二进制表示的位数.只能说我做题依旧太死板,让求和就按规矩求和,多考虑一下就能发 ...