Code

Ruby: Blocks

posted in: Ruby, Software Engineering | 0

What are Blocks?

Blocks in Ruby are chunks of code that are — almost — nothing but chunks of code. A block is not a method. It is not a proc or a lambda. And, despite the popular statement that “in Ruby, everything is an object,” a block is not an object, either.

I say “almost” nothing but a chunk of code because a Ruby block is a just a bit more than a chunk of code, too. It can accept arguments. It returns a value. And, since it has permanent access to the context in which it is created, it is also a closure.

Blocks permit a chunk of code to be passed to a method. The method can then invoke the block as needed. This is called yielding. The method executes, and can yield control to the block. when desired.

These are the rules for blocks:

  • Blocks can’t stand alone — they must follow a method invocation
  • A block argument is optional in a method invocation
  • All methods implicitly accept a block argument
  • Methods may ignore a block argument
  • If a method attempts to invoke its block argument, and the caller does not provide one, the method will throw an error

Invoking a Block

The simplest way to invoke a block is to use the yield keyword. Here’s an example:

Ruby has two different syntaxes for blocks. Here’s an example of each, both passed to a method called #runBlock:

Ruby convention uses bracket notation for one-line blocks, and the do notation for larger blocks. While it is possible to use bracket notation for multiple-line blocks, Rubyists don’t consider it good practice.

Note also that the yield keyword accepts arguments. Here, the block invocation passes the #runBlock‘s name argument to the block’s n argument.

Internally, Ruby compiles a method, and the block that is passed to it, as two separate entities. When the method executes, it can call the block at some specific point or points.

Blocks in Ruby’s Native Methods

The most common use of blocks is in conjunction with Ruby’s various iterator methods (#each, #map, #select, etc.). With these, the iterator method provides the process of iteration, and the block provides the specifics of what each iteration of the iterator does. The iterator implements the process of iteration, and defines what it will do with the result of each iteration. The block defines what that result will be.

Here’s an example, using the #map method. In this bit of code, the #addTwo method accepts an array argument. The array calls the #map method, passing it a block. The method iterates through the array, passing its elements one by one to the block. The block adds two to the element and returns it.

Ruby’s #map method iterates through its receiver. (A receiver is a caller of a method. With the #map method, the receiver is usually an array, but it can be an instance of any class that includes Enumerable.) Each element in the receiver gets passed to a supplied block. The block returns a value, and the #map method pushes this value into a new array. When the iteration of the receiver is complete, the #map method returns the new array.

Here’s one more example, using the #select method. The #select method also iterates through its receiver in the same way as the #map method. The block returns a true or false value. If true, the #select method pushes the element into a new array. If false the method does nothing. When the iteration of the receiver is complete, the #select method returns the new array.

The #getElements method accepts an array argument. The array calls the #select method, passing it a block. The method iterates through the array, passing the elements one by one to the block. The block returns true for each element that is an even number; the #select method pushes these elements onto the array that it returns.

Using Custom Blocks

Beyond using Ruby’s iterator methods, we can use blocks in any situation where we might want to pass flow control of a method to a chunk of code that the caller provides. Suppose, for example, we want to implement a progress notification method. The method will yield to a block every so many seconds, as specified in an argument. So:

This #showProg method accepts an interval argument. Line 11 is where the block argument gets injected into the method (using yield). If the block returns true, the loop exits. The elapsed variable gets passed to the yield invocation.

If you were going to use something like this in the real world, you would have to use multiple threads or Ruby’s Async gem to keep #showProg from blocking whatever it was reporting progress on. But we can simulate a long-running process to show how the yield invocation works, and to demonstrate that the block controls custom behavior.

Here we simulate a 10-second process, with a progress notification every 2 seconds.

Note that the block has access to the start variable declared in the main context. This is because, unlike Ruby methods, Ruby blocks are closures.

Here’s another call to #showProg with a different block, which demonstrates that you can use blocks to customize the behavior of the method:

So here, we have a 20-second process, with a progress notification (in French) every 5 seconds.

Advantages of Blocks

These two different blocks injected in to the same method show the flexibility that yielding to blocks affords. A method can do something very generic like iterating through a collection of items, and then, for each iteration, pass control of what to do to the caller in the form of yielding to a passed-in block.

Ruby blocks are an example of the Inversion of Control or IOC pattern (more info here). One of the perceived advantages of the Inversion of Control model is this decoupling of the repeatable aspects of a problem from its one-off aspects. Separating out the block allows a separate focus on what gets repeated with every block call, and that repetition makes for very robust code.

Related Articles

This article is one of a series of four. Here are the other three:
Ruby: Scope and Closures
Ruby: Procs, Lambdas and Bindings
Ruby: Block Parameters and Return Values