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

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

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

In Part 1, we covered:

  • Definitions of Code Block, Proc, Lambda, and Closure
  • Constructions
  • Calling a Code Block/Proc/Lambda
  • Passing and Returning Procs

In Part 2, we covered:

  • Scopes, Universes, and Lunch Boxes
  • Differences between Procs and Lambdas

 

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

  Proc <> Code Block Conversion and Ampersand(&)

  • 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!


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 blockso that the code block can be executed later
  • A proc, similar to all other objects, can be passed into a method as an argument, and a method executes 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 to 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 to it later?

The answers are no and no.

Because you call a proc and you yield to 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 to 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 code block into the method. We also indeed call the “block” inside the method. What actually happens, thanks to the ampersand(&), is that the passed-in block gets 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 is converted to a proc, and the resulting proc gets 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 code block and appends the code block to the method being called.

In line 6, we prepend an ampersand (&) to the proc, which gets converted into a code block, and append the resulting code block to the method. The method later yields to the code block.

 

A Code Block vs An Argument In the Context of A Method.

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

Appending a code block to a method and passing an argument to 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 as wearing a pair of pants!

You can append a code block to a method and passing arguments to the method at the same time. But appending a code block to a method is different than passing an argument to a method.

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

The give_me_another_block method is expecting an argument. But with the ampersand prepended to a proc, we convert the proc to a  code block and append it to the method. Although it looks like we pass an argument to the method, we actually append a code 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 also append a block to it. The same as you wearing a jacket and a pair of pants at the same time!

 

The “magical” ampersand (&)

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 remind you of any old friends? like .map(&:upcase) ? What are their relationships? Let’s think about that for a second …

The answer is, in .map(&:upcase), the ampersand(&) is doing exactly the same thing as it does for &pr: it calls pr.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.

Share your answer in the comment section. 😀


Thanks for reading.

If you find this article helpful, please pass it around.🙃

Don’t forget to subscribe!

Leave a Comment