[ZZ] Matz Talk -- Block & Closure

Loop Abstraction with Blocks

Bill Venners: Ruby supports blocks and closures. What are blocks and closures, and how are they used?

Yukihiro Matsumoto: Blocks are basically nameless functions. You may be familiar with the lambda from other languages like Lisp or Python. Basically, you can pass a nameless function to another function, and then that function can invoke the passed-in nameless function. For example, a function could perform iteration by passing one item at a time to the nameless function. This is a common style, called higher order function style, among languages that can handle functions as first class objects. Lisp does it. Python does it .Even C does it with function pointers. Many other languages do this style of programming. In Ruby, the difference is mainly a different kind of syntax for higher order functions. In other languages, you have to specify explicitly that a function can accept another function as an argument. But in Ruby, any method can be called with a block as an implicit argument. Inside the method, you can call the block using the yield keyword with a value.

Bill Venners: What is the benefit of blocks?

Yukihiro Matsumoto: Basically, blocks are designed for loop abstraction. The most basic usage of blocks is to let you define your own way for iterating over the items.

For example, if you have a list, sequence, vector, or array, you can iterate forward by using the method provided by the standard library. But what if you want to iterate backwards from the end to the beginning? In C, you have to set up four things: an index, a start value, an end comparison, and an increment. This is not good, because it reveals internal details of the list. We want to hide that logic. By using blocks, we can hide the loop logic inside the method or function. So for example by calling list.reverse_each , you can do a reverse iteration over the list without knowing how the list is implemented on the inside.

Bill Venners: I just pass in a block that's going to do whatever I want to do with each element, and it's up to the list to know how to go backwards. In other words, I pass as a block whatever code I would have put inside the for loop in C.

Yukihiro Matsumoto: Yes, and that also means you can define many ways to iterate. You could provide a forward way to iterate, a backward way, and so on. It's up to you. C# has an iterator, but it has just one iterator per class. In Ruby you can have an arbitrary number of iterators if you want. If you have a tree class, for example, which you think people will want to traverse depth first and breadth first, you can provide both kinds of traversal by providing two different methods.

Bill Venners: Let me see if I understand this. In Java, they abstract iteration with Iterators. A client can ask a Collection for an Iterator, for example. But the client must use a for loop that runs through and processes the items returned by the Iterator. Inside the for loop, I have "the code" that I want to perform on each item, so that for loop always shows up in the client code. With blocks, I don't call a method to get an Iterator back, I call a method and pass as a block "the code" I want to process each item of the iteration. Is the benefit of the block approach that it takes a little bit of code, the for loop, out of each client?

Yukihiro Matsumoto: The details of how to iterate should belong to the service provider class. The client should know as little as possible. That was the original purpose of blocks. In fact, in early versions of Ruby, the methods called with blocks were referred to as iterators, because they were designed to iterate. But in the history of Ruby, the role of blocks was later enhanced from loop abstraction to anything.

Bill Venners: For example...

Yukihiro Matsumoto: For example, we can create a closure out of a block. A closure is a nameless function the way it is done in Lisp. You can pass around a nameless function object, the closure, to another method to customize the behavior of the method. As another example, if you have a sort method to sort an array or list, you can pass a block to define how to compare the elements. This is not iteration. This is not a loop. But it is using blocks.

Using Closures
Bill Venners: What makes a block a closure?

Yukihiro Matsumoto: A closure object has code to run, the executable, and state around the code, the scope. So you capture the environment, namely the local variables, in the closure. As a result, you can refer to the local variables inside a closure. Even after the function has returned, and its local scope has been destroyed, the local variables remain in existence as part of the closure object. When no one refers to the closure anymore, it's garbage collected, and the local variables go away.

Bill Venners: So the local variables are basically being shared between the closure and the method? If the closure updates the variable, the method sees it. And if the method updates the variable, the closure sees it.

Yukihiro Matsumoto: Yes, local variables are shared between the closure and the method. It's a real closure. It's not just a copy.

Bill Venners: What is the benefit of a real closure? Once I make a block into an object, what can I do with it?

Yukihiro Matsumoto: You can reconvert a closure back into a block, so a closure can be used anywhere a block can be used. Often, closures are used to store the status of a block into an instance variable, because once you convert a block into a closure, it is an object that can by referenced by a variable. And of course closures can be used like they are used in other languages, such as passing around the object to customize behavior of methods. If you want to pass some code to customize a method, you can of course just pass a block. But if you want to pass the same code to more than two methods -- this is a very rare case, but if you really want to do that -- you can convert the block into a closure, and pass that same closure object to multiple methods.

Bill Venners: OK, but what is the benefit of having the context? The distinction that makes Ruby's closure a real closure is that it captures the context, the local variables and so on. What benefit do I get from having the context in addition to the code that I don't get by just being able to pass a chunk of code around as an object?

Yukihiro Matsumoto: Actually, to tell the truth, the first reason is to respect the history of Lisp. Lisp provided real closures, and I wanted to follow that.

Bill Venners: One difference I can see is that data is actually shared between the closure objects and the method. I imagine I could always pass any needed context data into a regular, non-closure, block as parameters, but then the block would just have a copy of the context, not the real thing. It's not sharing the context. Sharing is what's going on in a closure that's different from a plain old function object.

Yukihiro Matsumoto: Yes, and that sharing allows you to do some interesting code demos, but I think it's not that useful in the daily lives of programmers. It doesn't matter that much. The plain copy, like it's done in Java's inner classes for example, works in most cases. But in Ruby closures, I wanted to respect the Lisp culture.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值