Blocks, Procs, and Enumerable

Joël Quenneville

The Enumerable module is one of my favorite parts of Ruby. It allows us to perform operations like #each, #map, #inject, and #any? on collection objects, most notably Array. It’s all much cleaner, readable, and semantic than the nested for loops I used to write in other languages.

Enumerable is made possible by leveraging one of Ruby’s more powerful constructs: blocks. They typically take the form of:

collection.each { |item| puts item }

or

collection.map do |item|
  item * 2
end

More cryptically, you may also see:

collection.all?(&:valid?)

or

collection.inject(:&)

Let’s take a closer look at what’s going on here.

Blocks

Blocks allow methods to take arbitrary snippets of code as arguments and run them. The pattern of passing in an anonymous function into a method is commonly used across multiple languages. JavaScript, for example, is notorious for its use of deeply nested anonymous callback functions.

item.action(function(result) {
  result.otherAction(function(newResult) {
    console.log(newResult);
  });
});

In Ruby, you might express “multiply each element of this collection by 2 and return the result” by passing the anonymous function n * 2 to the Enumerable#map method:

[1, 2, 3].map { |n| n * 2 }
# => [2, 4, 6]

Procs

The concept of passing functions as arguments to other functions is quite powerful. Methods that have this ability are known as higher order functions. It is common (particularly in functional languages) to want to pass in a named function instead of an anonymous one. Ruby allows us to do that with Procs.

Proc is Ruby’s function object. We can pass a proc object instead of a block to any method by prefixing it with &. For example we could use the named function double in our previous snippet:

double = Proc.new { |n| n * 2 }

[1, 2, 3].map(&double)
# => [2, 4, 6]

#to_proc

Ruby does this clever trick where it doesn’t immediately try to run the Proc passed into the function but instead calls #to_proc on it before execution. This allows us to pass in any object that responds to #to_proc.

class Doubler
  def double(n)
    n * 2
  end

  def to_proc
    method(:double).to_proc
  end
end

doubler = Doubler.new

[1, 2, 3].map(&doubler)
# => [2, 4, 6]

Symbols

Passing in an object that responds to to_proc might seem like a cool but useless party trick that wouldn’t be used in a production codebase. That’s where Symbol comes in. It defines #to_proc as a function that sends the message of the same name as the symbol to the target object.

For example:

[1,2,3].map(&:to_s)
# => ["1", "2", "3"]

sends the message :to_s to each item in the array.

To get a better idea of how this works, we can try to replicate this behavior on String.

class String
  def to_proc
    method_name = self # the string is the method name
    Proc.new { |item| item.send(method_name) }
  end
end

[1, 2, 3].map(&"to_s")
# => ["1", "2", "3"]

This allows us to write terser and more readable code. For example, “are all the numbers even?” can be expressed as numbers.all?(&:even?) instead of numbers.all? { |n| n.even? }.

Enumerable#inject

inject (aka reduce) is one of the most powerful methods provided by Enumerable. In fact, all of the other methods provided by Enumerable could be written in terms of inject (This general concept is called catamorphisms. This paper takes a more detailed look at the topic). However, inject is weird. It takes a block just like any other method in Enumerable. But there’s a twist. Taking a look at the docs:

Combines all elements of enum by applying a binary operation, specified by a block or a symbol that names a method or operator.

This means that it can accept a symbol instead of a block and that the symbol will be used to send that message to the objects, just like Symbol#to_proc.

So you where you would use a traditional block syntax with Symbol#to_proc:

[1, 2, 3].inject(&:+)
# => 6

you could just use the bare symbol

[1, 2, 3].inject(:+)
# => 6

This can lead to the confusing expression:

[true, false, true].inject(:&)
# => false

At first glance this may look like someone tried to use Symbol#to_proc but forgot to fill in the method name. However, note that the & and : are flipped. Symbol#to_proc uses &:, not :&.

What’s actually happening is that inject is using the method & to combine the elements: true & false & true. The equivalent using Symbol#to_proc would be the even stranger looking

[true, false, true].inject(&:&)
# => false