14.MatZ谈Ruby中的块和闭包

本文为采访稿。

带块的循环

Bill Venners:Ruby支持块和闭包。什么是块和闭包,如何使用?

松本行弘(Yukihiro Matsumoto):块基本上是无名的功能。您可能对lambda很熟悉,来自其他语言,例如Lisp或Python。基本上,您可以将无名函数传递给另一个函数,然后该函数可以调用传入的无名函数。例如,一个函数可以通过一次将一项传递给无名函数来执行迭代。在可以将函数作为一等对象处理的语言中,这是一种常见的样式,称为高阶函数样式。Lisp做到了,Python做到了,甚至C都使用了函数指针。许多其他语言也采用这种编程风格。在Ruby中,区别主要是高阶函数的语法不同。在其他语言中,您必须明确指定一个函数可以接受另一个函数作为参数。但是在Ruby中,任何方法都可以使用块作为隐式参数来调用。在方法内部yield 带有值的关键字。

Bill Venners:块有什么好处?

松本行弘(Yukihiro Matsumoto):基本上,块是为循环抽象设计的。块的最基本用法是让您定义自己的迭代项目的方式。

例如,如果您有一个列表,序列,向量或数组,则可以使用标准库提供的方法来迭代。但是,如果您想从头到尾迭代,该怎么办?在C语言中,您必须设置四项内容:索引,起始值,结束比较和增量。这不好,因为它会显示列表的内部详细信息。我们想隐藏这种逻辑。通过使用块,我们可以将循环逻辑隐藏在方法或函数内部。因此,例如,通过调用list.reverse_each,您可以对列表进行反向迭代,而无需知道列表是如何在内部实现的。

Bill Venners:我只是传递了一个块,该块将对每个元素执行我想做的任何事情,这取决于要知道如何倒退的列表。换句话说,我会将要放在C中的for循环中的任何代码作为一个块传递。

松本行弘(Yukihiro Matsumoto):是的,这也意味着您可以定义许多迭代方法。您可以提供一种向前迭代的方法,一种向后迭代的方法,等等。由你决定。C#有一个迭代器,但是每个类只有一个迭代器。如果需要,在Ruby中可以有任意数量的迭代器。例如,如果您有一个树类,您认为人们将要首先遍历深度和宽度,则可以通过提供两种不同的方法来提供两种遍历。

Bill Venners:让我看看我是否理解这一点。在Java中,他们使用Iterator来抽象迭代 。例如,客户端可以问 Collection一个Iterator。但是客户端必须使用一个for循环,该循环运行并处理Iterator返回的项目。在for循环中,我具有要在每个项目上执行的“代码”,因此for循环始终显示在客户端代码中。对于块,我不调用方法来获得Iterator 回报,而是调用方法并作为块传递“代码”,以处理迭代的每个项目。块方法的好处是,它需要从每个客户端中获取一些代码,即for循环吗?

松本行弘:如何进行迭代的细节应该属于服务提供者类。客户应该了解的越少越好。那是块的初衷。实际上,在早期的Ruby版本中,用块调用的方法称为迭代器,因为它们被设计为进行迭代。但是在Ruby的历史中,块的作用后来从循环抽象扩展到任何东西。

Bill Venners:例如…

松本行弘(Yukihiro Matsumoto):例如,我们可以从一个块中创建一个闭包。闭包是一种无名函数,就像在Lisp中一样。您可以将无名函数对象(闭包)传递给另一个方法,以自定义方法的行为。再举一个例子,如果您有一种对数组或列表进行排序的排序方法,则可以传递一个块来定义如何比较元素。这不是迭代。这不是循环。但是它正在使用块。

使用闭包

Bill Venners:是什么导致闭包?

松本行弘(Yukihiro Matsumoto):一个闭包对象具有要运行的代码,可执行文件以及围绕代码的状态,作用域。因此,您可以在闭包中捕获环境,即局部变量。结果,您可以引用闭包内部的局部变量。即使在函数返回并且其局部作用域已被破坏之后,局部变量仍作为闭包对象的一部分存在。如果再也没有人引用该闭包,那么它将被垃圾回收,并且局部变量将消失。

Bill Venners:那么局部变量基本上是在闭包和方法之间共享的?如果闭包更新了变量,则该方法将看到它。如果该方法更新了变量,则闭包将看到它。

松本行弘:是的,局部变量在闭包和方法之间共享。这是一个真正的封闭。这不仅仅是副本。

Bill Venners:真正的闭包有什么好处?一旦将块变成对象,该怎么办?

松本行弘(Yukihiro Matsumoto):您可以将闭包转换回一个块,因此可以在可以使用块的任何地方使用闭包。通常,闭包用于将块的状态存储到实例变量中,因为一旦将块转换为闭包,该对象便可以被变量引用。当然,闭包也可以像在其他语言中一样使用,例如传递对象以自定义方法的行为。如果要传递一些代码以自定义方法,则当然可以传递一个代码块。但是,如果您要将同一代码传递给两个以上的方法(这是一种非常罕见的情况,但是如果您确实希望这样做),则可以将块转换为闭包,然后将同一闭包对象传递给多个方法。

Bill Venners:好的,但是拥有上下文有什么好处?使Ruby的闭包成为真正的闭包的区别在于它捕获了上下文,局部变量等。除了拥有上下文之外,我还能通过将一大堆代码作为对象来获取代码而又获得了什么好处?

松本行弘(Yukihiro Matsumoto):实际上,说实话,第一个原因是要尊重Lisp的历史。Lisp提供了真正的闭包,我想遵循它。

Bill Venners:我可以看到的一个区别是,数据实际上是在闭包对象和方法之间共享的。我想我总是可以将任何需要的上下文数据作为参数传递给一个常规的,非封闭的块,但随后该块将仅具有上下文的副本,而不是真实的副本。它不共享上下文。共享是一个闭包中发生的事情,它不同于普通的旧函数对象。

松本行弘(Yukihiro Matsumoto):是的,这种共享使您可以进行一些有趣的代码演示,但是我认为它在程序员的日常生活中并没有那么有用。没关系。普通副本,例如在Java内部类中完成的副本,在大多数情况下都可以使用。但是在Ruby闭包中,我想尊重Lisp文化。

原创文章,作者:huoxiaoqiang,如若转载,请注明出处:https://www.huoxiaoqiang.com/ruby/rubyhigh/1556.html

发表评论

电子邮件地址不会被公开。 必填项已用*标注