这篇访谈是几年前Artima.com网站对Ruby创始人Matz的访谈。Artima的访谈一般都比较深入技术层面,如果想加深对各种语言特性的了解,Artima的访谈是非常值得一看的。这篇讲述Ruby的blocks和closure结构。
Bill Venners:
Ruby支持blocks 和Closure 结构。什么是Ruby的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 接口实现抽象迭代的,例如,调用程序可以让llection 来实现 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 文化的致意。
访谈就到这里,希望看了这篇访谈,能使你对Ruby的blocks和closure结构有更加深入的了解。
【编辑推荐】