首先看下延续的定义:

拿科幻片中的场景做比喻的话,延续有点像时空门,这扇门在某个时间上的某个地点被设立,当我们打开这扇门走进去,出来的时候就会从锁定的那个时间和那个地点重新开始。

利用延续,我们可以方便的实现一些更复杂的程序控制状态,例如:非局部退出,异常处理, generators和协程等.

下面从一段实现非局部跳转的Scheme程序片段来深入理解延续.

(define (search-element element lst)
    (display (call/cc (lambda (break)
        (for-each (lambda (item) (if (equal? item element) (break #t))) lst)
        #f)))
)

这个函数的功能是从给定的一组数字中搜索某一特定的数字,如果找到输出#t,否则输出#f.

> (search-element 3 '(1 2 3 4))
#t
> (search-element 0 '(1 2 3 4))
#f

首先必须先介绍call/cc,它的作用是捕获当前的延续.下面是TSPL中对call/cc的介绍:

  • call/cc用于捕获当前的 continuation,需要给它提供一个单参函数作为参数调用.在这个单参函数的
    函数体内被捕获的continuation绑定到了传入的参数上.如果在函数体内没用直接或间接的调用continuation
    那么函数的返回值就是call/cc的返回值.如果在函数体内调用了continuation,那么传递给它的参数就是call/cc的返回值.

从上述的描述中可以知道,延续的表现形式很像一个函数,调用这个延续就可以进入那扇时空门,回到我们设立时空门的那个时间和那个地点.

在回到上面的例子,在这个例子中我们捕获到的延续可以如下表示:

(define (search-element element lst)
       (display _)
)

因为延续被绑定到了break,(break #t)相当于回到display这个执行点上,把_替换成#t然后继续执行.

现在对这个程序段稍加修改来更好的研究延续:

(define c1 10)
(define val1 100)

(define (search-element element lst)
    (display "begin search-element\n")
    (display (call/cc (lambda (break)
        (display "here\n")
        (set! c1 break)
        (for-each (lambda (item) (if (equal? item element) (break #t))) lst)
        #f)))
    (display " end of search-element\n")
    (display "val1:")(display val1)
)

我们把捕捉到的延续绑定到了一个全局变量c1上,以使得作为call/cc参数的函数体内也可以调用延续.

> (search-element 3 '(1 2 3 4))
begin search-element
here
#t end of search-element
val1:100

> (c1 `k)
k end of search-element
val1:100

> (set! val1 1000)
> (c1 `c)
c end of search-element
val1:1000

我们来看下输出,第一次调用的输出比后面的输出多了两行

begin search-element
here

这说明了我们调用延续的时候,的确是从第二个display那里继续执行的.而后面我们改变了全局变量val1的值,在后续调用延续的时候
这种改变也被查觉到了, 这说明延续保存的只是调用链和局部变量.

20岁的那年你设立了时空门,40岁的时候你从时空门回到过去,但你依旧还是40岁.

下面再看一个复杂点的例子一个generator.

(define (for-each proc items)
  (define (iter things)
    (cond ((null? things))
        (else
            (proc (car things))
            (iter (cdr things)))))
 (iter items))

(define (generate-one-element-at-a-time lst)
  ;; Hand the next item from a-list to "return" or an end-of-list marker
  (define (control-state return)
    (for-each
     (lambda (element)
       (call/cc
        (lambda (resume-here)
          ;; Grab the current continuation
          (set! control-state resume-here) ;; !!!
          (return element))))
     lst)
    (return 'end))

  (define (generator)
    (call/cc control-state))
  ;; Return the generator
  generator)   

看下输出:

> (define generate-digit (generate-one-element-at-a-time '(0 1 2)))
> (generate-digit)
0
> (generate-digit)
1
> (generate-digit)
2
> (generate-digit)
end
> (generate-digit)
end

每次调用(generate-digit)都会按序从创建时传入的数字序列中返回一个数字,如果到达序列的最后
则返回end.

分析下执行流程,首先将generate-digit定义成(generate-one-element-at-a-time '(0 1 2))的返回值
,也就是过程generator,所以(generate-digit)实际调用的就是(generator).

第一次调用(generate-digit)的时候,call/cc捕获到的延续在control-state中被绑定到名字return.
所以每当调用return就会回到generator (call/cc)的地方,继续执行后面的流程.

control-state中,将lambda函数

  (lambda (element)
   (call/cc
    (lambda (resume-here)
     (set! control-state resume-here)
      (return element))))

作为参数调用for-each,在for-each中这个lambda函数被绑定到名字proc,proc被调用的时候捕获延续并绑定到名字resume-here.
当这个延续被调用的时候,执行流程就回到(proc (car things))然后继续执行后面的(iter (cdr things)))))

需要注意的是,在这个lambda函数中control-state被替换成了resume-here所以除了第一次以外,其余对generator
的调用实际上调用的都是延续resume-here.

下面把程序做个小的调整:

(define c 0)

(define (for-each proc items)
  (define (iter things)
    (cond ((null? things))
        (else
            (proc (car things))
            (iter (cdr things)))))
 (iter items))

(define (generate-one-element-at-a-time lst)
  (define (control-state return)
    (for-each
     (lambda (element)
       (call/cc
        (lambda (resume-here)
          (cond ((> c 0))
            (else
                (set! c 1)
                (set! control-state resume-here)
             ))
          (return element))))
     lst)
    (return 'end))

  (define (generator)
    (call/cc control-state))
  generator)

再看下输出:

> (define generate-digit (generate-one-element-at-a-time '(0 1 2)))
> (generate-digit)
0
> (generate-digit)
1
> (generate-digit)
1
> (generate-digit)
1
> (generate-digit)
1

这次只在第一次调用proc的时候才将control-state设置为resume-here,也就是说以后的每次调用,实际上都是第一次调用proc时的延续,
而在这个延续中,相关的变量things始终是(1,2),所以无论调用多少次(generate-digit)始终都是返回1.

最后贴一段用延续实现的协程来结束这篇博文.

    ;一个简单的,用continuation实现的协程接口
    (define current-coro '());当前获得运行权的coro

    ;创建coro并返回,fun不会立即执行,由start-run执行
    (define (make-coro fun)
        (define coro (list #f #f))
        (let ((ret (call/cc (lambda (k) (begin
            (set-context! coro k)
            (list 'magic-kenny coro))))))
            (if (and (pair? ret) (eq? 'magic-kenny (car ret)))
                (cadr ret)
                ;如果下面代码被执行,则是从switch-to调用过来的
                (begin (let ((result (fun ret)))
                       (set-context! coro #f)
                       (set! current-coro (get-from coro))
                       ((get-context (get-from coro)) result)));fun执行完成后要回到调用者处
            )
        )
    )

    (define (get-context coro) (car coro))
    (define (set-context! coro context) (set-car! coro context))
    (define (get-from coro) (cadr coro))
    (define (set-from! coro from) (set-car! (cdr coro) from))

    (define (switch-to from to arg)
        (let ((ret
              (call/cc (lambda (k)
                    (set-from! to from)
                    (set! current-coro to)
                    (set-context! from k)
                    ((get-context to) arg)
                    arg))))
         ret)
    )

    ;启动一个coro的运行,那个coro将会从它在创建时传入的函数开始运行
    (define (start-run coro . arg)
        (let ((param (if (null? arg) arg (car arg))))
            (if (null? current-coro) (set! current-coro (make-coro #f)))
            (switch-to current-coro coro param))
    )

    ;将运行权交给另一个coro
    (define (yield coro . arg)
        (let ((param (if (null? arg) arg (car arg))))
            (switch-to current-coro coro param)))

    ;将运行权还给原来把运行权让给自己的那个coro
    (define (resume . arg)
        (let ((param (if (null? arg) arg (car arg))))
            (switch-to current-coro (get-from current-coro) param)))

    ;;;;;;;test procedure below;;;;;;;;;;;;
    (define (fun-coro-a arg)
        (display "fun-coro-a\n")
        (yield (make-coro fun-coro-b))
        (display "coro-a end\n")
        "end"
    )

    (define (fun-coro-b arg)
        (display "fun-coro-b\n")
        (display "fun-coro-b end\n")
        "end"
    )

    (define (test-coro1)
        (start-run (make-coro fun-coro-a))
    )

    (define (fun-coro-a-2 arg)
        (define coro-new (make-coro fun-coro-b-2))
        (define (iter)
            (display "fun-coro-a\n")
            (display (yield coro-new 1))(newline)
            (iter)
        )
        (iter)
    )

    (define (fun-coro-b-2 arg)
        (define (iter)
            (display "fun-coro-b\n")
            (display(resume 2))(newline)
            (iter)
        )
        (iter)
    )

    (define (test-coro2)
        (start-run (make-coro fun-coro-a-2))
    )

参考:

延续(continuation)的更多相关文章

  1. http2协议翻译(转)

    超文本传输协议版本 2 IETF HTTP2草案(draft-ietf-httpbis-http2-13) 摘要 本规范描述了一种优化的超文本传输协议(HTTP).HTTP/2通过引进报头字段压缩以及 ...

  2. [转载]协程-cooperative multitasking

    [转载]协程三讲 http://ravenw.com/blog/2011/08/24/coroutine-part-1-defination-and-classification-of-corouti ...

  3. TSPL学习笔记(1)

    扩展语法(Syntactic extensions) 扩展语法就是通过核心语法或已经定义的扩展语法创建一种新的语法模式. Scheme核心语法模式包括: 顶层定义 常量 变量 过程应用 '(quote ...

  4. 无废话JavaScript(下)

    五.函数式 这个可不是JavaScript的发明,它的发明人已经死了,而他的这个发明还在困扰着我们……如同爱迪生的灯泡还在照耀着我们. 其实函数式语言很简单,它就是一种与命令式语言同样“完备”的语言实 ...

  5. Pro ASP.Net Core MVC 6th 第四章

    第四章 C# 关键特征 在本章中,我描述了Web应用程序开发中使用的C#特征,这些特征尚未被广泛理解或经常引起混淆. 这不是关于C#的书,但是,我仅为每个特征提供一个简单的例子,以便您可以按照本书其余 ...

  6. Parallel Programming-Task Result && Continuation Task

    本文主要介绍带有返回值的Task和Continuation Task 带返回值的Task Continuation Task ContinueWhenAll即多任务延续 一.带返回值的Task 1.1 ...

  7. 移动信息化不能延续PC时代的痛

    当下,随着移动时代的到来,手机功能逐步完善,各个行业针对这一现象纷纷制定了相应的营销计划,于是霎时间兴起了一股网上订票/网上订饭/网上预约的热潮. 而对于IT行业,成为企业信息化最火的代名词莫过于移动 ...

  8. UnicodeDecodeError: 'utf8' codec can't decode byte 0xce in position 47: invalid continuation byte

  9. 尾递归(Tail Recursion)和Continuation

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

随机推荐

  1. Odoo 8.0 new API 之Environment

    """ An environment wraps data for ORM records: - :attr:`cr`, the current database cur ...

  2. CentOS完美搭建Redis3.0集群并附测试

    线上的统一聊天和推送项目使用的是redis主从,redis版本2.8.6 redis主从和mysql主从目的差不多,但redis主从配置很简单,主要在从节点配置文件指定主节点ip和端口:slaveof ...

  3. android获取状态栏高度

    获取android屏幕上状态栏的高度方法网上很多这里不再敖述,只举一个例子 Rect rect = new Rect();getWindow().getDecorView().getWindowVis ...

  4. JavaScript中数组操作

    var arr1=new Array(); arr1.push(1);//在数组的中末尾添加元素,并返回新的长度 arr1.push(2);//在数组的中末尾添加元素,并返回新的长度 arr1.pop ...

  5. Android ADB使用

    ADB全称Android Debug Bridge, 是android sdk里的一个工具, 用这个工具可以直接操作管理android模拟器或者真实的andriod设备(如G1手机). 它的主要功能有 ...

  6. android listview 重用view导致的选择混乱问题

    20150526 listview是常用的控件,经常用自定义的adapter,为了提高显示效率,常利用view的重用方式防止重绘,但因为重用利用的是旧的view,常导致显示的数据会由于position ...

  7. Java 代码学习之数组的初始化

    我们都很熟悉Java中的数组,它具有查询快,增删慢的特点.但是通常我们自认为很了解它的用法,却容易忽略一些小细节.今天通过一段代码来简单了解数组初始化中的一些我们容易忽略的地方. package da ...

  8. vue_ajax 请求

    yarn add vue-resource axios npm install --save axios pubsub-js // import VueResource from "vue- ...

  9. Delphi创建ActiveX控件,实现安全接口及无界面代码

    Delphi创建OCX控件非常的方便,但IE调用时弹出的安全认证非常麻烦,有时OCX也不需要界面,IE调用时需要隐藏,非常不方便.在DELPHI中创建OCX实现安全接口和创建事件中修改部分代码 实现安 ...

  10. HTML 弹出遮罩层一(遮罩层和内容标签嵌套)

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...