本文来自网易云社区

作者:陆艺

上学时看了SICP之后就对scheme这个看上去比较古怪的语言产生了兴趣. 虽然书里并没有涉及scheme太多语法以及语言上特性的一些东西, 作为一个喜欢折腾的人, 手贱翻了翻Google, 了解到有continuation这样一个吊吊的东西, 于是借着有限的脑力学习了下.

基本概念

continuation的中文一般都叫做"续延"(还蛮好听的), 不过解释起来比较麻烦, 还是结合代码来看更好理解一点.

首先scheme里的continuation(太长了, 下文缩写为cont吧)的基本形式是长这个样子的:

(call/cc(lambda (cc); do some computation)
)

call/cccall-with-current-continuation的缩写, lambda的参数cc就是当前环境的一个cont, 可以理解为当前的调用栈, 它知道自己未来需要执行的过程. 那么怎么表示"未来的计算"这样一个概念呢? 在编程语言中自然就是函数了, 而在scheme中就是过程(procedure), 更一般的就用lambda来表示. cc就是这样一个只接受一个参数的lambda, 而调用(cc val)则是整个cont动作的关键----它将流程回cont定义的位置, 并以val代换为其计算出的结果.

来暴力理解一下, 把(call/cc ..) (rest code..)看做这样一个结构[] (rest code ..), 在(cc val)的时候直接回到了[]的地方, 并将其替换为了val.

看一个简单(似乎不是特别简单)的例子:

(+ 1 (call/cc)(lambda (cc) (+ 2 (cc 3)))
)

一步步来:

  1. 整个block可以看做(+ 1 [])

  2. 再看[]内部: (lambda (cc) (+ 2 (cc 3))), 这个lambda的body是会被执行的

  3. (cc 3)以3为参数调用cont, 则跳转到其定义的地方[]将其替换

  4. 最后变为(+ 1 3), 结果为4

  5. (+ 2 ..)的部分并没有luan3用 :P

基本用法

Jump out

这个用法有个名字叫"非本地退出(non-local exit)", 说白了就是现代编程语言里break啦, exit啦这些东西.

  • scheme没有这些关键字

  • scheme觉得它们不够old-school

  • scheme决定这么搞

(define (search wanted? lst)(call/cc(lambda (return)(for-each (lambda (e)(if (wanted? e) (return e)))lst)#f))
)

功能很直白, 从一个list里找到符合条件的元素, 不存在就返回#f. return的用法是直接从for-each的循环中跳出了. 这里最后的#f可不可以写成(return #f)? 当然可以, 不过整个lambda都计算完了, 自然是返回最后一个计算值, 所以可以直接省略啦.

另一个例子

(define (list-product list)(call/cc(lambda (exit)(let iter ((rest lst))        (cond((null? rest) 1)          ((zero? (car rest)) (exit 0))          (else (* (car rest) (iter (cdr rest))))))))
)

对一个list完成fold计算乘积的操作, 中途如果遇到0则立即退出, 避免了不必要的遍历.

Jump back

在scheme里, cont和lambda一样都是一等公民, 它自然也可以被当做参数抛来抛去, 或者跟其他变量进行绑定.

(define return #f)(+ 1 (call/cc(lambda (cont) (set! return cont) 1))
)

老样子一步一步看:

  1. 全局定义一个return(绑定啥值无所谓)

  2. 替换cont形式: (+ 1 [])

  3. 在lambda body中, return与cont[]绑定!

  4. 1作为返回值, 则(+ 1 [])结果为2

然而并没有结束, 此时return本身已经作为一个"+1器"存在了, 它代表的cont即(+ 1 []), 每次调用(return v)便会得到v + 1.

Jump out and Jump back

看到这里, 可以感觉到cont是能作为程序流控制的手段的, 用来完成一些比较时髦的动作, 比如大家都喜欢的协程 :P

所以接下来看一个模拟协程计算的较复杂的例子:

(define (hefty-computation do-other-stuff) ; 主要复杂计算(let loop ((n 5))(display "Hefty computation: ")(display n)(newline)(set! do-other-stuff (call/cc do-other-stuff)) ; (1)(display "Hefty computation (b)\n")(set! do-other-stuff (call/cc do-other-stuff)) ; (3)(display "Hefty computation (c)\n")(set! do-other-stuff (call/cc do-other-stuff))(if (> n 0) (loop (- n 1))))
)(define (superfluous-computation do-other-stuff) ; 次要计算(let loop ()(for-each (lambda (item)(display item)(newline)(set! do-other-stuff (call/cc do-other-stuff))) ; (2)'("Straight up." "Quarter after." "Half past."  "Quarter til."))(loop) ; this trigger inf loop)
)(hefty-computation superfluous-computation)

为了大家(我自己)能简单的看懂, 我们走的慢一些:

  1. 分别定义了两个过程: 主要计算和次要计算; 以调用主要计算开始, 参数为次要计算本身

  2. 开始主要计算, 进入loop(第一次), 打印"Hefty computation: 5"

  3. (1)处call/cc以次要计算为参数, 结合后者定义, 则有(为了方便, 分别记两个两个过程的参数为do1和do2):

  4. 主要计算产生第一个cont, (set! do1 []-1)

    1. 开始次要计算, 参数do2为上一步的[]-1, 进入loop(第一次), for-each打印列表中第一个"Straight up."

    2. 到达(2) (call/cc do2), 此时次要计算产生一个cont, 记为[]-2, 同时完成do2也就是[]-1的计算, 流程回到了(1), 且将do1绑定到了[]-2

  5. 从(1)后继续主要计算, 打印"Hefty computation (b)"

  6. 主要计算(3)处产生第二个cont[]-3, 形式和上次一样; 由于参数do1实际上是[]-2, 则:

    1. 跳转到[]-2即(2)处继续执行, 此时set![]-2的返回值即[]-3绑定到do2

    2. for-each的第二个循环打印"Quarter after."

    3. 流程又到(2)处, (call/cc []-3), 并产生当前的cont[]-4, 作为[]-3的参数; 执行[]-3的计算, 于是又回到了(3)处

  7. do1绑定为[]-4, 且打印"Hefty computation (c)"

  8. 接下来的过程类似上面, 即不断的在主要计算和次要计算之间跳来跳去(和协程的切换是一样的)

看上去似乎有点晕, 总结一下这个用法的关键在于:

(call/cc cc)实际上是将当前的cont作为参数contcc的结果, 然后通过执行cc跳转到cont创建时的地方; 之后通过set!将之前的cont(即cc的结果)保存下来, 然后再次通过(call/cc cont)跳回去, 如此往复.

最后一个例子

(let* ((yin  ((lambda (cc) (write-char #\@) cc) ; proc(call/cc (lambda (c) c))))        ; arg(yang ((lambda (cc) (write-char #\*) cc) ; proc(call/cc (lambda (c) c)))))       ; arg(yin yang)
)

这是网上流传很久的yin-yang puzzle, 程序会不停的交替打印"@*@**@***@ ...". 这个例子大家可以尝试自己拿纸笔推一下(虽然过程特别的绕, 但是通过不断代换的笨办法还是可以一战的), 或者可以看一下这个解答就比较好理解了, stay calm :P

时至今日我仍然没有完整的学完scheme.. 这里也只是简单的介绍了下自己对continuation及用法的基本理解, 欢迎各位大大多加指正.

ref:

  • http://geek-zh.net/cps-and-call-cc/

  • http://www.ituring.com.cn/article/53793

  • http://community.schemewiki.org/?call-with-current-continuation

  • http://stackoverflow.com/questions/2694679/how-does-the-yin-yang-puzzle-work/36513942#36513942

本文来自网易实践者社区,经作者陆艺权发布

相关文章:
【推荐】 漫画解读“跨视图粒度计算”,了解有数分析利器

初识Continuation相关推荐

  1. 如何识别图片验证码?

    全自动区分计算机和人类的图灵测试(Completely Automated Public Turing test to tell Computers and Humans Apart,简称CAPTCH ...

  2. 集合、深浅拷贝、文件操作(读、写、追加)函数初识(参数)

    小数据池 #int ==比较数值 is 比较内存地址 id 测试内存地址 #str 不能含有特俗字符 单个元素*数字,不能超过21 i1 = 'a'*20 i = 'a'*20 id一样 i1 = ' ...

  3. day3----编码-集合-深浅copy-文件操作-函数初识

    day3----编码-集合-深浅copy-文件操作-函数初识 本文档主要内容: 一 编码 二 集合 三 深浅copy 四 文件操作 五 函数初识 首先,我们来看看两个字符串的比较 打开cmd,进入do ...

  4. ⑥python模块初识、pyc和PyCodeObject

    一.模块初识(一) 模块,也叫库.库有标准库第三方库. 注意事项:文件名不能和导入的模块名相同 1. sys模块 import sys print(sys.path) #打印环境变量 print(sy ...

  5. 初识java类的接口实现

    初识java类的接口实现 如果两个类之间不存在继承关系,且两个类都想实现同一个接口,两个类都必须实现接口中全部方法,否则报语法错误 如果两个类之间存在继承关系也想实现同一个接口,父类如果实现了某个接口 ...

  6. vba 编辑combobox内容_初识Visual Basic编辑器并建立一段简单的代码

    大家好,从今日开始我正式推出"VBA之EXCEL应用"教程,这个教程是面向初学人员的教程,教程一共三册,十七个章节,从简单的录制宏实现一直讲到窗体的搭建,都是我们在利用EXCEL工 ...

  7. 16.1、python初识面向对象(1)

    初识面向对象 楔子 你现在是一家游戏公司的开发人员,现在需要你开发一款叫做<人狗大战>的游戏,你就思考呀,人狗作战,那至少需要2个角色,一个是人, 一个是狗,且人和狗都有不同的技能,比如人 ...

  8. 精通Python网络爬虫:核心技术、框架与项目实战.1.1 初识网络爬虫

    摘要 网络爬虫也叫做网络机器人,可以代替人们自动地在互联网中进行数据信息的采集与整理.在大数据时代,信息的采集是一项重要的工作,如果单纯靠人力进行信息采集,不仅低效繁琐,搜集的成本也会提高.此时,我们 ...

  9. 初识mysql数据字段属性_MySQL数据库~~~~初识、基础数据类型

    一 数据库初识 1.1 什么是数据库 数据库(DataBase,简称DB),简而言之可视为电子化的文件柜----存储电子文件的处所,用户可以对文件中的数据运行新增,截取,更新,删除等操作. 所谓数据库 ...

最新文章

  1. Solr 4.x定时、实时增量索引 - 修改、删除和新增索引
  2. 用户体验设计常犯10个逻辑谬误
  3. 如何deactivate Material delta download
  4. springboot导包显示不存在_BOOT项目依赖另外一个模块的包,开发的时候都正常,执行clean package打包成jar时却提示依赖的模块包不存在。...
  5. 递归基础之N皇后问题
  6. 草稿 断开式绑定combobox 1128
  7. Service混合开启笔记(startService+bindService)
  8. 紫米创始人张峰兼任小米笔记本总经理
  9. htaccess 实现网址缩短
  10. U-Time巡回完美收官 演讲嘉宾干货分享:数据篇
  11. [2018.07.21 T3] Booom
  12. Eclipse汉化教程
  13. 006 与PHP无关的EXCEL分割字符串
  14. java开源引擎easyrule_【Urule介绍】开源可视化规则引擎
  15. 机房布线的最高境界 | 最后的暗黑系,真是亮瞎眼
  16. 网站安全渗透测试团队公司解决防护方案
  17. 自习室 《大学生创新创业课程设计》
  18. (转)windows 7兼容软件列表搜集加转帖
  19. 阿里云助力中小企业建站 在线免费自助建站成新用户首选
  20. 米家Zigbee系列传感器软件设计总结

热门文章

  1. 用Midjourney画个美女,AI绘画也太强大了!!! - 第8篇
  2. R语言 K-M生存分析,ggplot2制作好看的生存曲线
  3. 找到多个名为spring_web的片段。这是不合法的相对排序。有关详细信息,请参阅Servlet规范的第8.2.2 2c节。考虑使用绝对排序。
  4. Oracle分页查询存储过程(适用于单表查询)
  5. c语言流水灯程序 16,16个发光二极管流水灯程序
  6. iterm2分屏切换
  7. erp中三大订单CO、PO、MO各是代表什么?
  8. 基于ASAM ODS标准的试验数字化平台-WDP
  9. 如何实现自动化按图片搜索淘宝商品(拍立淘)功能?拍立淘API接口item_search_img
  10. [画板]画PCB和玩植物大战僵尸