Proc <> Code Block Conversion and Ampersand(&) in Ruby

Sihui Huang
5 min readJun 6, 2017

This is the last part of a three-parts series where we take a close looks at Code Block, Proc, Lambda, and Closure in Ruby.

In Part 1, we covered:

  1. Definitions of Code Block, Proc, Lambda, and Closure
  2. Constructions
  3. Calling a Code Block/Proc/Lambda
  4. Passing and Returning Procs

In Part 2, we covered:

5. Scopes, Universes, and Lunch Boxes [FUN STUFF]

6. Differences between Procs and Lambdas

In this post, the last part of the series, we will discuss:

7. Proc <> Code Block Conversion and ampersand(&) [FUN STUFF]

  • A code block vs an argument in the context of a method
  • Behind the scenes of [‘hello’, ‘world’].map(&:upcase)

Let’s dive into it!

7. Proc <> Code Block Conversion and ampersand(&)

Hold on! Before we jump into this topic, let’s briefly revisit some foundations covered in Part 1:

  • A proc is an object that contains a code block so that the code block can be executed later
  • A proc, similar with all other objects, can be passed into a method as an argument, and a method execute a code block inside a proc by calling the proc.
  • A code block can be attached to a method directly, and a method executes the code block by yielding it.

Now, here are the questions:

  • can we attach a code block to a method and call it later?
  • can we pass a proc into a method and yield it later?

The answers are no and no.

Because you call a proc and you yield a code block.

BUT, we can:

  • attach a code block to a method, convert it to a proc, and call the proc later
  • pass a proc into a method, convert it to a code block, and yield it later

The conversion between a proc and a code block happens with the help of ampersand(&).

Let’s look at an example:

In the code above, we clearly do pass in a block into the method. We also indeed call the “block” inside the method. What actually happened, thanks to the ampersand(&), was that the passed-in block got converted to a proc.

You can think of the ampersand as a magic wand: when it’s prepended to an argument of a method, it converts the appended code block to a proc and assigns the result to the argument name.

In the example, the appended code block was converted to a proc, and the resulting proc was assigned to the variable named “block”.

The other way works as well:

If an ampersand is prepended to a proc, it converts the proc into a block and appends the block to the method being called.

In line 006, we prepended an ampersand to the proc, which got converted into a block, and appended it to the method. The method later yielded the block.

There’s one thing worth emphasizing: the ampersand converts a proc into a block and appends the block to the method, NOT passing the block in as an argument to the method.

Appending a code block to a method and passing an argument into a method are TWO DIFFERENT THINGS. Although they can both be used by the method, they are two different things.

They are as different as how a jacket is different than a pair of pants. You can wear a jack. You can wear a pair of pants. You can wear a jack and a pair of pants at the same time. You can use the material of a pair of pants, convert it into a jacket, and wear it. But wearing a jacket converted from a pair of pants is not the same a wearing a pair of pants!

What happens when you confuse a code block as an argument? irb will yell at you:

In give_me_another_block method is expecting an argument. But with the ampersand prepended to a proc, we convert the proc to a block and append it to the method. Although it looks like we pass an argument to the method, we actually append a block to the method without passing any arguments in. That’s why irb gives us the wrong number of arguments method.

Now we pass the method an argument and append it a block. The same as you wearing a jacket and a pair of pants at the same time!

Enough fun with jackets and pants! Let’s switch our attention to the “magical” ampersand: what exactly is happening?

When we do &pr we are actually doing pr.to_proc.call:

  • we first call the to_proc method on pr,
  • then we call the proc returned by to_proc.

The magic wand ampersand(&) is nothing but another ruby syntactic sugar.

Does &pr reminds you of any old friends you have? like .map(&:upcase)? What are their relationships? Let’s think about that for a second …

Photo credit: https://pbs.twimg.com

The answer is, in .map(&:upcase), the ampersand is doing exactly the same thing as it does for &pr: it calls to_proc.call.

Consider [‘Hi’, ‘Yo’].map(&:upcase).

On one hand, we know it is the same as:

  • [‘Hi’, ‘Yo’].map { |element| element.upcase }, which is the same as
  • [‘Hi’, ‘Yo’].map { |element| element.send(:upcase) }

On the other hand, we know &:upcase is the same as :upcase.to_proc.call

So [‘Hi’, ‘Yo’].map(&:upcase) is the same as:

  • [‘Hi’, ‘Yo’].map { |element| :upcase.to_proc.call(element) }

How does

[‘Hi’, ‘Yo’].map {|element| :upcase.to_proc.call(element) }

becomes

[‘Hi’, ‘Yo’].map { |element| element.send(:upcase) }

??

Well, the key lies in the definition of to_proc in the Symbol class.

And I will leave it as a homework for you :)

--

--