// 上一篇:控制结构(10)指令序列(opcode)


[注释]:

  • 这个笔记系列需要告一个段落了,收尾部分整理下几个时髦(The New Old Things)结构。
  • 后面打算开一个算法方面的,重新学习下算法方面的知识。

前面的文章里,在所有异步的地方都只假设通过callback的方式实现,而不用其他新的原子结构。实际上,如果我们进一步要求:

  • 不使用return函数返回
  • 一个函数执行的最后一步要么退出,要么调用其他函数来传递结果。

那么,这样的一个函数在最后一个动作之后是不需要保存Stack的。如果这个接收结果的被调函数本身总是以函数参数的形式传递,则我们称这样的手法为“CPS”:

In functional programming, continuation-passing style (CPS) is a style of programming in which control is passed explicitly in the form of a continuation. This is contrasted with direct style, which is the usual style of programming. --wiki:CPS

wiki里面举例以lisp函数语言的case居多,对于习惯命令式语言的人来说,阅读起来可能感觉不是很强烈。以JavaScript的视角,可以阅读下面这三篇文章:

以下翻译上述链接里面的例子.


假设有这样一个命令式的递归函数:

function calc(l,r){
return l+r;
} function vist(tree){
if(tree==null){
return 0;
}else{
var l = vist(tree.left);
var r = vist(tree.right);
return calc(l,r);
}
}

我们希望一步步把它改写成CPS风格的代码,第一步把visit函数的返回去掉,改用一个回调函数k来接收结果:

function vist(tree, k){
if(tree==null){
k(0);
}else{
// var l = vist(tree.left);
// var r = visit(tree.right);
k(calc(l,r));
}
}

注意到上面的代码注释了两行,因为我们希望visit没有返回值,则这两行代码不能再调用visit的原版代码。怎么做呢?

通过反复执行下面的步骤来达成:

  • 被注释掉的部分,最后一个操作的结果应该传递给一个“回调函数”,假设是C
  • 使用了该结果的后续代码应该被“卷入”到C里面。

于是上述代码变为:

function visit(tree,k){
if(tree==null){
k(0);
}else{
// var l = vist(tree.left); // 这个callback消灭了var r = visit(tree.right);和k(calc(l,r));
function kRight(r){
k(calc(l,r));
} // 此时可以把kRight作为visit的第2个参数,这是一个CPS
visit(tree.right, kRight);
}
}

如此,我们还差一个// var l = vist(tree.left);没有消灭,再来一次:

function visit(tree, k){
if(tree==null){
k(0);
}else{
function kLeft(l){
function kRight(r){
k(calc(l,r));
}
visit(tree.right, kRight);
}
visit(tree.left, kLeft);
}
}

除了calc那个地方依然是命令式代码,其他部分都已经是CPS风格代码。但这个地方的消除就留给读者做一个练习。当一个代码全部都变成函数调用的时候,我们可以让每个函数调用的地方实际上都变成生成包含发生这个函数调用所需要的数据,而不是直接执行它。但在此之前,我们先让上面函数的参数合并成一个(一个简单的结构体就可以搞定它)。

function visit(args){
if(args.tree==null){
args.k(0);
}else{
function kLeft(l){
function kRight(r){
args.k(calc(l,r));
}
visit(args.tree.right, kRight);
}
visit(args.tree.left, kLeft);
}
}

好了,现在,我们先造一个生成包含发生这个函数调用所需要的数据的辅助函数:

var continuation = null;
function createContinuation(newFun,newArgs){
continuation = {func: newFun, args: newArgs};
}

那么,visit函数就会被改写成一个让每个函数调用的地方实际上都变成生成包含发生这个函数调用所需要的数据,而不是直接执行它的函数:

function visit(args){
if(args.tree==null){
createContinuation(k, 0);
}else{
function kLeft(l){
function kRight(r){
//args.k(calc(l,r));
createContinuation(k, calc(l,r));
}
//visit(args.tree.right, kRight);
createContinuation(visit, {tree:args.tree.right,k:kRight});
}
//visit(args.tree.left, kLeft);
createContinuation(visit, {tree:args.tree.left,k:kLeft});
}
}

那么,原来的调用哪去了呢?现在这个visit似乎只是不断在改写全局变量continuation而已?别急,马上你就会明白怎么回事了:

function runIterate(){
while(continuation!=null){
var f = continuation.func;
var args = continuation.args;
continuation = null;
f(args); //思考:此处发生了什么?
}
}

runIterate函数可以轻易的在 continuation被改写的间隙执行这个 continuation所代表的函数动作!调用如下:

createContinuation(visit, {tree: mytree, k: print});
runIterate();

当然,可以改写为递归的执行:

function runRecursive(){
if(continuation){
var f = continuation.func;
var args = continuation.args;
continuation = null;
f(args);
runRecursive();
}
}

这样就完成了手写CPS的过程。这个过程中蕴含一些重要的思想。例如函数调用的地方不实际调用,而只是产生描述调用的数据,通过另一个执行函数去执行实际的函数调用。这个过程事实上是一个生成指令数据/解释指令执行的过程。这个思想在任何需要的地方都是一个不错的选择。

控制结构(11): Continuation passing style(CPS)的更多相关文章

  1. feilong's blog | 目录

    每次把新博客的链接分享到技术群里,我常常会附带一句:蚂蚁搬家.事实上也确实如此,坚持1篇1篇的把自己做过.思考过.阅读过.使用过的技术和教育相关的知识.方法.随笔.索引记录下来,并持续去改进它们,希望 ...

  2. scheme Continuation

    Continuation Pass Style在函数式编程(FP)中有一种被称为Continuation Passing Style(CPS)的风格.在这种风格的背后所蕴含的思想就是将处理中可变的一部 ...

  3. 尾递归(Tail Recursion)和Continuation

    递归: 就是函数调用自己. func() { foo(); func(); bar(); } 尾调用:就是在函数的最后,调用函数(包括自己). foo(){ return bar(); } 尾递归:就 ...

  4. Haskell语言学习笔记(29)CPS

    CPS (Continuation Passing Style) CPS(延续传递风格)是指函数不把处理结果作为返回值返回而是把处理结果传递给下一个函数的编码风格. 与此相对,函数把处理结果作为返回值 ...

  5. 【理论面试篇】收集整理来自网络上的一些常见的 经典前端、H5面试题 Web前端开发面试题

    ##2017.10.30收集 面试技巧 5.1 面试形式 1)        一般而言,小公司做笔试题:大公司面谈项目经验:做地图的一定考算法 2)        面试官喜欢什么样的人 ü  技术好. ...

  6. JavaScript内存优化

    JavaScript内存优化 相对C/C++ 而言,我们所用的JavaScript 在内存这一方面的处理已经让我们在开发中更注重业务逻辑的编写.但是随着业务的不断复杂化,单页面应用.移动HTML5 应 ...

  7. jQuery中的Deferred-详解和使用

    首先,为什么要使用Deferred? 先来看一段AJAX的代码: var data; $.get('api/data', function(resp) { data = resp.data; }); ...

  8. Understanding continuations

    原文地址http://fsharpforfunandprofit.com/posts/computation-expressions-continuations/ 上一篇中我们看到复杂代码是如何通过使 ...

  9. 【转】对 Go 语言的综合评价

    以前写过一些对 Go 语言的负面评价.现在看来,虽然那些评价大部分属实,然而却由于言辞激烈,没有点明具体问题,难以让某些人信服.在经过几个月实际使用 Go 来构造网站之后,我觉得现在是时候对它作一些更 ...

随机推荐

  1. vi的三种模式

    一般指令模式 (command mode)以 vi 打开一个文件就直接进入一般指令模式了(这是默认的模式,也简称为一般模式) .在这个模式中, 你可以使用“上下左右”按键来移动光标,你可以使用“删除字 ...

  2. Java开发笔记(三十)大小数BigDecimal

    前面介绍的BigInteger只能表达任意整数,但不能表达小数,要想表达任意小数,还需专门的大小数类型BigDecimal.如果说设计BigInteger的目的是替代int和long类型,那么设计Bi ...

  3. Spring Boot 整合 docker

    一.什么是docker ? 简介 Docker是一个开源的引擎,可以轻松的为任何应用创建一个轻量级的.可移植的.自给自足的容器.开发者在笔记本上编译测试通过的容器可以批量地在生产环境中部署,包括VMs ...

  4. java日期 Calendar类的使用

    举例: import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; public clas ...

  5. Easyui datagrid 设置内容超过单元格宽度时自动换行显示

    datagrid 设置内容超过单元格宽度时自动换行显示 by:授客 QQ:1033553122 测试环境 jquery-easyui-1.5.3 问题描述 单元格内容超过单元格宽度不会自动化换行.如下 ...

  6. Unity Profiler的使用

    选中Development Build.Autoconnect Profiler和Script Debugging三个选项,如下图所示. 点击Build And Run按钮,将会编译项目并安装APK到 ...

  7. 【学习笔记】【Design idea】一、Java异常的设计思想、性能相关、笔记

    1.前言: 异常.本该是多么优雅的东西,然而,得全靠自己在零散的信息中汇集. 学习笔记保持更新. 2.教材(参考资料) 其他 ①受检异常与非受检异常:https://www.cnblogs.com/j ...

  8. Vue一个案例引发的递归组件的使用

    今天我们继续使用 Vue 的撸我们的实战项目,只有在实战中我们才会领悟更多,光纸上谈兵然并卵,继上篇我们的<Vue一个案例引发的动态组件与全局事件绑定总结> 之后,今天来聊一聊我们如何在项 ...

  9. ASP.NET Zero--前期要求

    前期要求 需要以下工具才能使用ASP.NET Zero Core解决方案: Visual Studio 2017 + Visual Studio扩展: Bundler&Minifier Web ...

  10. AjaxPro2完整入门教程

    一.目录 简单类型数据传送(介绍缓存,访问Session等) 表类型数据传送 数组类型数据传送(包含自定义类型数据) 二.环境搭建 1.这里本人用的是VS2012. 2.新建一个空的Web项目(.NE ...