注: Matz 就是著名的编程语言 Ruby 的创始人(日本人)
这段对话翻译自 artima.com 网站对 Matz 的访谈的第三部分。
英文文章原址: http://www.artima.com/intv/closures.html
未经本人同意,不要任何地方转载这篇翻译文章
(最先发表在本人的网站 http://www.eiffelqiu.com)。
使用 Blocks 做循环抽象
Bill Venners:
Ruby 支持 blocks 和 Closure 结构. 什么是 blocks 和 Closure, 他们如何使用?
Yukihiro Matsumoto:
Blocks 基本上就是匿名函数。你可能熟悉诸如Lisp 或 Python等其他语言中
的 Lambda 函数。 你可以向另外一个函数传递一个匿名函数,这个函数可以
调用这个被传递过来的匿名函数。例如, 函数可以通过一次传递给匿名函数一
个元素来执行循环迭代。在那些可以将函数当作第一类型的编程语言中,这是个
通常的方式,称为高排序函数样式。 Lisp 可以这样,Python 也是如此,甚至
就连C 也可以通过函数指针实现这点。很多其他语言也可以做这样的编程。
在 Ruby 中,不同之处只是在高排序函数语法风格上有所不同。在其他语言中,你
必须显示的指出一个函数可以接受另外一个函数作为参数。但是在Ruby 中,任何
方法都可以 Block 作为一个隐性参数被调用。在方法中,你可以使用 yield 关键字
和一个值来调用 block.
Bill Venners:
Block 的好处是什么?
Yukihiro Matsumoto:
基本上,Block 是被设计来做循环迭代抽象的。Block 最基本的使用就是让你
以自己的方式定义如何循环迭代。
例如,如果你有一个列表,序列,矢量组或者数组,你可以通过使用标准库中
提供的方法来实现向前循环迭代,但是如果你想从后往前实现循环迭代呢?如
果使用 C 语言,你得先设置四件事情:一个索引,一个起始值,一个结束条件
和一个递增变量。这种方式不好,因为它暴露了列表的内部实现方法,我们希
望能够隐藏内部逻辑,通过使用 Block 我们可以将内部循环迭代的方式隐藏在
一个方法或者函数中。比如,调用 list.reverse_each,你可以对一个列表实
现一个反向的循环迭代,而不需要知道列表内部是如何实现的。
Bill Venners:
就是说,我传递一个 Block 结构,这个 Block 中的代码可对循环迭代中每个
元素做任何事情,至于如何反向遍历就取决于List 本身了。换句话说,我就
是把原本在 C 语言 Loop 循环中写的那些代码作为一个 Block 来传递。
Yukihiro Matsumoto:
对,这意味着你可以定义许多迭代的方式。你可以提供一种向前循环迭代的方
式,一种向后循环迭代的方式,等等。这全取决于你了。C#也有迭代器,但是
它对于每个类只有一个迭代器。在 Ruby 中你可以拥有任意数量的迭代器。例
如,如果你有一个 Tree 类,可以让人以深度优先或者广度优先的方式遍历,
你可以通过提供两种不同的方法来提供两种遍历方式。
Bill Venners:
让我想想是否我了解了这点,在 Java 中,它们是通过 Iterator 接口实现抽
象迭代的,例如,调用程序可以让 Collection 来实现 Iterator。但是调用程
序必须使用循环来遍历Iterator 返回的元素。在 For 循环中, 我的代码实
现对每个循环迭代的元素的处理,这样循环语句将总是显示在调用程序中。 使
用 Block , 我并不调用一个方法来获取一个迭代器,我只是调用一个方法,同
时将我希望对循环迭代中每个要处理的元素的处理代码作为一个 Block 块结
构传递给该函数。 Block 的好处是不是将一些代码从调用程序中的 for 循环
中提取出来。
Yukihiro Matsumoto:
实现循环迭代的具体细节应该属于提供这个功能的类。调用程序应该尽可能的少
知道这些。这就是 Block 结构的本来目的。实际上,在早期版本的 Ruby 中,
使用 Block 的方法被称为迭代器,因为它们就是被设计来实现循环迭代的。但
是在 Ruby发展过程中,Block 的用途在后来已经得到了很大的增强,从最初的循环
抽象到任何事情。
Bill Venners:
例如。。。。
Yukihiro Matsumoto:
我们可以从Block 中创建一个 Closure 对象,一个 Closure 对象就是像 Lisp
中实现的那种匿名函数。 你可以向任何方法传递一个匿名函数(即 Closure)来
自定义方法的行为。另外举个例子,如果你有一个排序的方法用于排序数组或者
列表,你可以定义一个 Block 来定义如何在元素之间进行比较,这不是循环迭代。
这不是个循环,但是它使用了 Block 。
Bill Venners:
什么使得 Block 成为了一个 Closure?
Yukihiro Matsumoto:
Closure 对象包含可以运行的代码,是可执行的,代码包含状态,执行范围。
也就是说在Closure 中你捕捉到运行环境,即局部变量。因此,你可以在一个
Closure 中引用局部变量,即是在函数已经返回之后,他的执行范围已经销毁掉,
局部变量依然作为一部分存在于Closure 对象中,当没有任何对象引用它的时候,
垃圾搜集器将处理它,局部变量将消失。
Bill Venners:
这么说,局部变量基本上是被方法和Closure 对象共享的?如果 Closure 对象
更新了变量,方法可以看到,如果方法更新了变量,Cosure 对象也可以看到。
Yukihiro Matsumoto:
是的,局部变量在Closure 和方法之间共享,这是真正的 Closure,它不仅仅是
复制。
Bill Venners:
一个真正的 Closure 有什么好处?一旦我将一个 Block 变为一个 Closure,我
能用它做什么?
Yukihiro Matsumoto:
你可以将一个 Closure 转换为一个 Block,所以 Closure 可以被用在任何 Block
可以使用的地方。通常, Closure 用来将一个 Block 的状态保存在一个实例变量中,
因为一旦你将一个 Block 转换为一个 Closure, 它就是一个通过变量可以引用的对
象了。当然Closure 也可以像其他语言中那样使用 ,例如传递给对象以实现对方法
行为的定义。如果你希望传递一些代码来自定义一个方法, 你当然可以传递给它一个
Block. 但是如果你想将同样的代码传递给两个方法(当然这是非常少见的情况),但
是如果你确实想这么做,你可以将一个 Block 转换为一个 Closure ,将同一个 Closure
传递给多个方法。
Bill Venners:
原来如此,但是获取上下文环境有什么好处呢?真正让 Ruby 的 Closure 不同的是
它捕捉运行时间的上下文环境,局部变量等等。那么到底拥有上下文环境有什么好
处是我们无法通过传递给对象一个代码块所获得的呢?
Yukihiro Matsumoto:
实际上,说实在的,最主要的原因是向 Lisp 语言表达敬意, Lisp 提供了真正的
Closure 结构,所以我希望继续提供这个功能。
Bill Venners:
我看到的一个不同之处是: 数据在Closure 对象和方法之间共享。我想我可以在
一个常规的非 Closure 结构的 Block 中放入任何需要的环境数据作为参数来传
递,但是 Block 仅仅是对环境数据的一份复制,并不是真正的 Closure. 它并没有
共享环境数据。共享是Closure 和普通的传统函数对象不同的地方。
Yukihiro Matsumoto:
是的,共享允许你做一些有趣的代码演示,但是我觉得它对于程序员的日常工作并
没有想象的那么有用。这没什么太大的关系,例如像 Java 的内部类那样的普通复
制,在许多场合都在使用。但是通过 Ruby 的Clousure 结构,我希望表达我对Lisp 文化的致意。
邱海峰(eiffelqiu)
mail: eiffel_q@163.com
weblog: http://mulder.5d.cn