How map(&:some_method) works

by Jason Swett,

The map method’s shorthand syntax

One of the most common uses of map in Ruby is to take an object and call some method on the object, like this.

[1, 2, 3].map { |number| number.to_s }

To save us from redundancy, Ruby has a shorthand version which is functionally equivalent to the above.

[1, 2, 3].map(&:to_s)

The shorthand version is nice but its syntax is a little mysterious. In this post I’ll explain why the syntax is what it is.

Passing symbols as blocks

Let’s leave the world of map for a moment and deal with a “regular” method.

I’m going to show you a method which takes a block and then demonstrate four different ways of passing a block to that method.

Side note: if you’re not too familiar with Proc objects yet, I would suggest reading my other posts on how Proc objects work and what the & in front of &block means before continuing.

First way: using a normal block

You’ve of course seen this way before. We call my_method and pass a regular block to it.

def my_method(&block)
  block.call("hello")
end

puts my_method { |value| value.upcase } # outputs "HELLO"

Second way: using Proc.new

If we wanted to, we could instead pass our block using Proc.new. Since my_method takes a block and not a Proc object, we would have to prefix Proc.new with an ampersand to convert the Proc object into a block.

(If you didn’t know, prefixing an expression with & will convert a proc to a block and a block to a proc. See this post for more details on how that works.)

def my_method(&block)
  block.call("hello")
end

puts my_method(&Proc.new { |value| value.upcase })

There would never really be a practical reason to express the syntax this way, but I wanted to show that it’s possible. This “second way” example will also connect the first way and the third way.

Third way: using to_proc

All symbols respond to a method called to_proc which returns a Proc object. If we do :upcase.to_proc, it gives us a Proc object that’s equivalent to what we would have gotten by doing Proc.new { |value| value.upcase }.

def my_method(&block)
  block.call("hello")
end

puts my_method(&:upcase.to_proc)

Fourth way: passing a symbol

I’ll show one final way. When Ruby sees an argument that’s prefixed with an ampersand, it attempts to call to_proc on the argument. So our to_proc on &:upcase.to_proc is actually superfluous. We can just pass &:upcase all by itself.

def my_method(&block)
  block.call("hello")
end

puts my_method(&:upcase)

What ultimately gets passed is the Proc object that results from calling :upcase.to_proc. Actually, more precisely, what gets passed is the block that results from calling &:upcase.to_proc, since the & converts the Proc object to a block.

Passing symbols to map

With the understanding of the above, you now know that this:

[1, 2, 3].map(&:to_s)

Is equivalent to this:

[1, 2, 3].map(&:to_s.to_proc)

Which is equivalent to this:

[1, 2, 3].map(&Proc.new { |number| number.to_s })

Which, finally, is equivalent to this:

[1, 2, 3].map { |number| number.to_s }

So, contrary to the way it may seem, there aren’t two different “versions” of the map method. The shorthand syntax is owing to the way that Ruby passes Proc objects.

Takeaways

  • When an argument is prefixed with &, Ruby attempts to call to_proc on it.
  • All symbols respond to the to_proc method.
  • There aren’t two different versions of the map method. The shorthand syntax is possible due to the two points above.

7 thoughts on “How map(&:some_method) works

  1. James Couball

    Thanks, Jason, this is a great article.In addition to learning how ‘&’ works, I went digging around for other articles and found this one:

    https://blog.pjam.me/posts/ruby-symbol-to-proc-the-short-version/

    which points out that the &:symbol syntax is not just for parameter-less methods (like to_s which doesn’t take a parameter). There is an example to sort an array using the spaceship operator (which takes two parameters):

    [ 5, 3, 1, 2, 4 ].sort(&:)

    Reply
    1. James Couball

      It looks like the sort example in the last line in my first comment was eaten by this commenting platform… using the word “spaceship_operator” in place of the actual spaceship operator, the example would look like this:

      [ 5, 3, 1, 2, 4 ].sort(&:spaceship_operator)

      Reply
    2. Micah Buckley-Farlee

      The spaceship operator only takes one parameter, not two. The operator method is called on the left hand side, taking the right hand side as a single parameter.

      Reply

Leave a Reply

Your email address will not be published.