Understanding Ruby Closures

Blocks, procs, lambdas, and methods available in Ruby are collectively called closures.

As useful as they can be, their slight differences at times, make you feel they are just different names for the same Ruby construct.

However, if you look closely, they have their differences and it takes an eager eye to figure them out.

During the course of this tutorial, we are going to analyse Ruby closures and you will learn:

What blocks, procs, lambdas, and methods are

How they differ from eachother

How you can use blocks, procs, lambdas, and methods in your own code

Blocks

The simplest kind of closure is a block.

A block is a chunk of code that can be passed to an object/method and is executed under the context of that object.

If you have worked with arrays, you have probably used an array's each method that allows you to iterate through the array's contents.

arr = [1, 2, 3, 4, 5]
arr.each do | element |
puts element
end

And here is the output.

1
2
3
4
5

In this case, the code between do and end which is puts element is a block.

Though this is a very simple block with a single line of code, blocks can be as long as hundred lines of code depending on what you are trying to achieve using a block.

Here is an example using an array's each_index method.

arr = [1, 2, 3, 4, 5]
arr.each_index do | index |
puts "The element at #{index} is #{arr[index]}"
puts "The square of #{arr[index]} is #{arr[index]**2}"
puts "The cube of #{arr[index]} is #{arr[index]**3}"
end

The code above produces the output.

The element at 0 is 1
The square of 1 is 1
The cube of 1 is 1
The element at 1 is 2
The square of 2 is 4
The cube of 2 is 8
The element at 2 is 3
The square of 3 is 9
The cube of 3 is 27
The element at 3 is 4
The square of 4 is 16
The cube of 4 is 64
The element at 4 is 5
The square of 5 is 25
The cube of 5 is 125

Notice the variables element and index that are enclosed within the | operator to pass into the block in both of our examples.

Depending on how the block is written, you can pass one or more variables into your blocks.

The delete_if method of the Hash class is an example. It deletes all elements from the hash for which the block evaluates to true.

There are a number of object methods in Ruby that accept blocks and execute them under the context of the object they are passed to.

I highly recommend you read the official Ruby documentation whenever working with any object. It is the best source to learn an object's specifics and will also help you come across methods that accept blocks as a parameter.

So, knowing how to pass blocks to the core object methods is all good, but in a way, it limits our possibilities if we do not know how to write our own methods that accept blocks.

Writing Methods That Accept Blocks

From our discussion on blocks so far, you might think that you are about to embark on the most complex parts of this tutorial. But this isn't the case.

Before we move to writing our own block-accepting methods, let us write a simple Post class that we can use to build upon throughout this tutorial.

We have created a post class with the attributes title, content, author, and publish_date. Using the attr_accessor method, we have made all these four attributes readable and writable. The object contructor which is the initialize method accepts all four of them as a parameter and simply sets the corresponding instance variables.

The next thing I want to talk about is the yield keyword.

As simple as it may sound, writing your own block-accepting methods is just a matter of using the yield keyword.

To demonstrate, I am going to write a block_inspect method on our post class, which will accept a block and pass in the post object's instance variable names and values to the given block.

In case of the post class, it would return [:@title, :@content, :@author, :@publish_date].

The instance_variable_get method simply returns the value for each of these instance variables. All you have to do is pass in the instance variable name.

So, instance_variable_get(:@title) would return the post's title and so on.

Inside our block_inspect method, we have called self.instance_variables which would return us the array [:@title, :@content, :@author, :@publish_date].

So basically, our code resolves to [:@title, :@content, :@author, :@publish_date].each which as we saw earlier, allows us to work with each element of this array.

Since we want our post attribute names to be human-readable, we need to convert them to strings and remove the prepended @ symbol. This is exactly what the line stringified_instance_variable_name = instance_variable.to_s.sub('@', '') does, saving the resulting string to the variable stringified_instance_variable_name.

The next part yield(stringified_instance_variable_name, self.instance_variable_get(instance_variable)) is pretty simple and interesting.

By using the yield keyword, we are simply instructing the compiler to take the block that was passed to this method, and pass it the post instance variable's name and value which are stringified_instance_variable_name, and self.instance_variable_get(instance_variable) respectively. These variables correspond to the variables passed to the block enclosed withing the | operator.

Since we have used the each method, this is done for all the post's instance variables.

Starting with the method signature, we have explicitly added block to the parameters list. The only change apart from that is calling the block using block.call and passing in the instance variable names and values so that they are available inside our block.

Also, notice that we have added & before block when specifying the function parameters. It means that blocks need to be passed by reference, and not value.

Procs

The simplest way to understand procs (short for procedures) is when you save your blocks to a variable, it is called a proc.

In other words, a block is actually a proc, only that it has been declared in-line and not saved to a variable.

Here is a proof of concept.

Let us write a very simple function that accepts a block as a parameter. We will only be echoing the class of the passed block inside the function.

def show_class(&block)
puts "The block class is #{block.class}" if block_given?
yield if block_given?
end
show_class do
puts "Hi! from inside the block"
end

The above code produces the output.

The block class is Proc
Hi! from inside the block

Voila!

Let us add another method to our post class to do the same inspection using a proc.

title = Title
Answer to life and universe is NilClass
content = Content
Answer to life and universe is NilClass
author = Author
Answer to life and universe is NilClass
publish_date = Publish_Date
Answer to life and universe is NilClass

As you can see, the class of answer_to_life_and_universe is printed NilClass for all the iterations. We could also have printed the variable answer_to_life_and_universe using puts answer_to_life_and_universe but since it is NIL, nothing is printed out.