Jumps

One of the signal features of Scheme is its support for jumps or nonlocal control. Specifically, Scheme allows program control to jump to arbitrary locations in the program, in contrast to the more restrained forms of program control flow allowed by conditionals and procedure calls. Scheme’s nonlocal control operator is a procedure named call‑with‑current‑continuation. We will see how this operator can be used to create a breathtaking variety of control idioms.

scheme 一个显著的特性是其支持跳转<Jump>或非局部控制<nonlocal contral>。具体地说,scheme 允许程序控制流跳转到程序的任意地方,不像通过条件语句和函数调用等控制跳转那么局限。
scheme 跳转操作是通过 call-with-current-continuation <简写形式:call/cc>过程来实现的。下面我们将会看到如何通过该过程来实现许多惊人的控制流程<control idioms>。

13.1  call‑with‑current‑continuation

The operator call‑with‑current‑continuation calls its argument, which must be a unary procedure, with a value called the “current continuation”. If nothing else, this explains the name of the operator. But it is a long name, and is often abbreviated call/cc.1

The current continuation at any point in the execution of a program is an abstraction of the rest of the program. Thus in the program

call-with-current-continuation 过程的参数是一个单参数<unary>过程,单参数过程的参数被绑定为"current continuation"。
 
在程序的任何地方,"current continuation" 都是 "the rest of the program" 的抽象。因此,下面语句中:
(+ 1 (call/cc
(lambda (k)
(+ 2 (k 3)))))

the rest of the program, from the point of view of the call/cc-application, is the following program-with-a-hole (with [] representing the hole):

call/cc 过程的 "the rest of the program" 就是下面的有一个 [] 的语句:

(+ 1 [])

In other words, this continuation is a program that will add 1 to whatever is used to fill its hole.

This is what the argument of call/cc is called with. Remember that the argument of call/cc is the procedure

也就是说,这个 continuation 就是用任何值来来替 [] 进而与 1 相加的程序。
 
下面是 call/cc 过程的参数<必须为一个单参数过程>:
(lambda (k)
(+ 2 (k 3)))

This procedure’s body applies the continuation (bound now to the parameterk) to the argument 3. This is when the unusual aspect of the continuation springs to the fore. The continuation call abruptly(突然地) abandons its own computation and replaces it with the rest of the program saved in k! In other words, the part of the procedure involving the addition of 2 is jettisoned(抛弃), and k’s argument 3 is sent directly to the program-with-the-hole:

<过程体 k 调用随后的语句不会被执行,直接跳转>
随后用 3 代替 []:
(+ 1 [])

The program now running is simply

(+ 1 3)

which returns 4. In sum,

(+ 1 (call/cc
(lambda (k)
(+ 2 (k 3)))))
=> 4

The above illustrates what is called an escaping continuation, one used to exit out of a computation (here: the (+ 2 []) computation). This is a useful property, but Scheme’s continuations can also be used to return to previously abandoned contexts, and indeed to invoke them many times. The “rest of the program” enshrined in a continuation is available whenever and how many ever times we choose to recall it, and this is what contributes to the great and sometimes confusing versatility of call/cc. As a quick example, type the following at the listener:

上面用例子说明了用来跳出计算的 escaping continuation。但是,在 scheme 中,continuation 也可以被用来多次跳转到程序已经执行过的地方。
先睹为快,看下面程序:
(define r #f)

(+ 1 (call/cc
(lambda (k)
(set! r k)
(+ 2 (k 3)))))
=> 4

The latter expression returns 4 as before. The difference between this use of call/cc and the previous example is that here we also store the continuation k in a global variable r.

Now we have a permanent record of the continuation in r. If we call it on a number, it will return that number incremented by 1:

最后一个表达式返回 4,这里的 call/cc 调用与前面的例子中有一个不同,就是在跳转前我们把 "the continuation" 绑定到 r 全局变量中。现在,我们就有了一个在 r 变量存储的 "the continuation" 记录,该 "the continuation" 为 (+ 1 []),因此,以后,我们以一个数值调用 r 时,都会使程序控制流跳转到程序中的 "the continuation" 的位置,并且用该数值代替 [] 进行以后的计算。
因此,当我们用 5 调用 r 时返回 6:
(r 5)
=> 6

Note that r will abandon its own continuation, which is better illustrated by embedding the call to r inside some context:

而把 r 调用嵌入在某些环境中更能证明 continuation 跳转的作用:

(+ 3 (r 5))
=> 6

r 调用会将程序控制流直接跳转到 "the continuation" (+ 1 []) 处,再用 5 替换 [],执行那里的代码。

The continuations provided by call/cc are thus abortive continuations.

13.2  Escaping continuations

Escaping continuations are the simplest use of call/cc and are very useful for programming procedure or loop exits. Consider a procedure list‑productthat takes a list of numbers and multiplies them. A straightforward recursive definition for list‑product is:

(define list-product
(lambda (s)
(let recur ((s s))为什么,不要不行
(if (null? s) 1
(* (car s) (recur (cdr s)))))))

There is a problem with this solution. If one of the elements in the list is 0, and if there are many elements after 0 in the list, then the answer is a foregone 预知conclusion. Yet, the code will have us go through many fruitless recursive calls to recur before producing the answer. This is where an escape continuation comes in handy. Using call/cc, we can rewrite the procedure as:

但是上面的计算方法有一个问题,就是列表中有一个元素为 0 并且其后还有许多个元素,当计算到 0 时,结果就可以确定为 0,而不用在与其后的元素相乘。此时,我们可以用 escaping continuations 重写 list-product 过程来避免这些无效的计算:

(define list-product
(lambda (s)
(call/cc
(lambda (exit)
(let recur ((s s))
(if (null? s) 1
(if (= (car s) 0) (exit 0)
(* (car s) (recur (cdr s))))))))))

If a 0 element is encountered, the continuation exit is called with 0, thereby avoiding further calls to recur.

13.3  Tree matching

A more involved example of continuation usage is the problem of determining if two trees (arbitrarily nested dotted pairs) have the same fringe, ie, the same elements (or leaves) in the same sequence. Eg,

先序排列是否相同

(same-fringe? '(1 (2 3)) '((1 2) 3))
=> #t (same-fringe? '(1 2 3) '(1 (3 2)))
=> #f

The purely functional approach is to flatten both trees and check if the results match.

一个简单的方法就是先计算出两个树的先序排列表再比较它们

define same-fringe?
(lambda (tree1 tree2)
(let loop ((ftree1 (flatten tree1))
(ftree2 (flatten tree2)))
(cond ((and (null? ftree1) (null? ftree2)) #t)
((or (null? ftree1) (null? ftree2)) #f)
((eqv? (car ftree1) (car ftree2))
(loop (cdr ftree1) (cdr ftree2)))
(else #f))))) (define flatten
(lambda (tree)
(cond ((null? tree) '())
((pair? (car tree))
(append (flatten (car tree))
(flatten (cdr tree))))
(else
(cons (car tree)
(flatten (cdr tree)))))))

However, this traverses the trees completely to flatten them, and then again till it finds non-matching elements. Furthermore, even the best flattening algorithms will require conses equal to the total number of leaves. (Destructively modifying the input trees is not an option.)

We can use call/cc to solve the problem without needless traversal and without any consing. Each tree is mapped to a generator, a procedure with internal state that successively produces the leaves of the tree in the left-to-right order that they occur in the tree.

但是这个效率比较低下。
 
我们可以用 call/cc 过程定义一个 tree->generator 过程来解决不必要的计算:
(define tree->generator
(lambda (tree)
(let ((caller '*))
(letrec
((generate-leaves
(lambda ()
(let loop ((tree tree))
(cond ((null? tree) 'skip)
((pair? tree)
(loop (car tree))
(loop (cdr tree)))
(else
(call/cc
(lambda (rest-of-tree)
(set! generate-leaves
(lambda ()
(rest-of-tree 'resume)))
(caller tree))))))
(caller '()))))
(lambda ()
(call/cc
(lambda (k)
(set! caller k)
(generate-leaves))))))))

When a generator created by tree‑>generator is called, it will store the continuation of its call in caller, so that it can know who to send the leaf to when it finds it. It then calls an internal procedure calledgenerate‑leaves which runs a loop traversing the tree from left to right. When the loop encounters a leaf, it will use caller to return the leaf as the generator’s result, but it will remember to store the rest of the loop (captured as a call/cc continuation) in the generate‑leaves variable. The next time the generator is called, the loop is resumed where it left off so it can hunt for the next leaf.

Note that the last thing generate‑leaves does, after the loop is done, is to return the empty list to the caller. Since the empty list is not a valid leaf value, we can use it to tell that the generator has no more leaves to generate.

The procedure same‑fringe? maps each of its tree arguments to a generator, and then calls these two generators alternately. It announces failure as soon as two non-matching leaves are found:

scheme@(guile-user)> (define gen (tree->generator '((1 2) 3)))
scheme@(guile-user)> (gen)
1
scheme@(guile-user)> (gen)
2
scheme@(guile-user)> (gen)
3
scheme@(guile-user)> (gen)
()
利用这一特性,当我们利用 tree->generator 过程返回值调用来比较两个树的每一个元素时,当两个被比较的元素不相同时,我们就可以判定这两个树的先序排列不相同直接返回而不用在去理会其他未比较的元素,tree->generator 版的 same-fringe? 过程如下:
(define same-fringe?
(lambda (tree1 tree2)
(let ((gen1 (tree->generator tree1))
(gen2 (tree->generator tree2)))
(let loop ()
(let ((leaf1 (gen1))
(leaf2 (gen2)))
(if (eqv? leaf1 leaf2)
(if (null? leaf1) #t (loop))
#f))))))

It is easy to see that the trees are traversed at most once, and in case of mismatch, the traversals extend only upto the leftmost mismatch. cons is not used.

一个更加容易理解的版本:

http://lispor.is-programmer.com/posts/23105.html

更多:

http://www.ccs.neu.edu/home/dorai/t-y-scheme/t-y-scheme-Z-H-15.html#node_chap_13

http://lispor.is-programmer.com/posts/23638.html

Teach Yourself Scheme in Fixnum Days 13 Jump跳转的更多相关文章

  1. Teach Yourself Scheme in Fixnum Days 6 recursion递归

    A procedure body can contain calls to other procedures, not least itself: (define factorial (lambda ...

  2. LeetCode 笔记系列13 Jump Game II [去掉不必要的计算]

    题目: Given an array of non-negative integers, you are initially positioned at the first index of the ...

  3. Github上的1000多本免费电子书重磅来袭!

    Github上的1000多本免费电子书重磅来袭!   以前 StackOverFlow 也给出了一个免费电子书列表,现在在Github上可以看到时刻保持更新的列表了. 瞥一眼下面的书籍分类目录,你就能 ...

  4. Github 的一个免费编程书籍列表

    Index Ada Agda Alef Android APL Arduino ASP.NET MVC Assembly Language Non-X86 AutoHotkey Autotools A ...

  5. racket学习-call/cc (let/cc)

    Drracket continuation 文中使用let/cc代替call/cc Racket文档中,let/cc说明为: (let/cc k body ...+) Equivalent to (c ...

  6. Lisp语言学习的书

    Scheme <How to Design Programs : An Introduction to Programming and Computing>(<程序设计方法>) ...

  7. UVA 1452 八 Jump

    Jump Time Limit:3000MS     Memory Limit:0KB     64bit IO Format:%lld & %llu Submit Status Practi ...

  8. Android Scheme协议与应用全解析

    URL Scheme 的作用 客户端应用可以向操作系统注册一个 URL Scheme,该 Scheme 用于从浏览器或其他应用中启动本应用. 通过指定的 URL 字段,可以让应用在被调起后直接打开某些 ...

  9. Android业务组件化之URL Scheme使用

    前言: 最近公司业务发展迅速,单一的项目工程不再适合公司发展需要,所以开始推进公司APP业务组件化,很荣幸自己能够牵头做这件事,经过研究实现组件化的通信方案通过URL Scheme,所以想着现在还是在 ...

随机推荐

  1. HAVING 子句 (SQL Server Compact)

    MSDN官方文献 原文地址:http://technet.microsoft.com/zh-cn/library/ms173260.aspx

  2. for

    1,cout在显示bool值之前将他们转换为int,但cout.setf(ios::boolalpha)函数调用设置了一个标记,标记命令cout显示true 和 false 而不是 1 和0;

  3. 深入理解linux网络技术内幕读书笔记(十)--帧的接收

    Table of Contents 1 概述 1.1 帧接收的中断处理 2 设备的开启与关闭 3 队列 4 通知内核帧已接收:NAPI和netif_rx 4.1 NAPI简介 4.1.1 NAPI优点 ...

  4. 获取当前位置信息-ios

    locationManager= [[CLLocationManager alloc] init];//位置管理器 locationManager.desiredAccuracy = kCLLocat ...

  5. 简析MFC中CString用作C字符串

      MFC中CString是一个方便的字符串操作的类, 然而很多函数需要传递字符指针, 这就需要进行CString和普通字符串的转换. 1.CString用作C字符串常量. 直接使用强制类型转换即可, ...

  6. 设计模式14---设计模式之命令模式(Command)(行为型)

    1.场景模拟 请用软件模拟开机过程 按下启动按钮 然后电源供电 主板开始加电自检 BIOS依次寻找其他设备的BIOS并且让他们初始化自检 开始检测CPU,内存,光盘,硬盘,光驱,串口,并口,软驱即插即 ...

  7. android常用软件下载资源链接

    最新内容请看:http://www.androiddevtools.cn/ https://github.com/inferjay/AndroidDevTools 官方adt下载地址:http://d ...

  8. Python进阶之路---1.5python数据类型-字符串

    字符串 *:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !important; ...

  9. iOS集成微信支付各种坑收录

    统一下单的参数要拼接成XML格式,使用AFN请求时要对参数转义,直接传入字典给AFN无法识别(这个接口微信demo中并没有提供示例) AFHTTPRequestOperationManager *ma ...

  10. Css的三大机制(特性):特殊性、继承、层叠详解

    继承(Inheritance)是从一个元素向其后代元素传递属性值所采用的机制.确定应当向一个元素应用那些值时,用户代理(浏览器)不仅要考虑继承,还要考虑声明的特殊性(specificity),另外需要 ...