Chances are you've seen and written code like users.map(&:first_name)
in ruby or rails applications many times. If you're new to Ruby, you may have wondered about this strange syntax. Let's demysify &:
and explain exactly how it works.
First we start with blocks and how to pass block arguments to methods.
Passing block arguments to methods and the &
We can think of a Block as a chunk of ruby code associated with method invocation. We know that any method invocation may be followed by a block and the method can invoke the code in that block with a yield
statement.
Here is an example. This method generates a sequence of n
numbers offset by a constant c
and passes them to the block.
def seq(n, c)
i = 0
while (i < n)
yield i + c
i += 1
end
end
And we can invoke that method like this
seq(5, 2) { |x| puts x }
In this case the block we passed to the method is anonymous. What if we wanted to explicitly refer to the block within the method? We can add a named block argument and precede it with an &
. If we do this, the block will be converted to a Proc
object. Then we can use the call
method of the Proc
object instead of the keyword yield
in this case (although yield
works too).
def seq(n, c, &b)
i = 0
while (i < n)
b.call(i + c)
i += 1
end
end
This changes the method definition. We still invoke the method the same way as before seq(5,2) { |x| puts x }
. So when would we use the &
with method invocation as well?
When &
is used before a Proc
object in a method invocation, it treats the Proc
as if it was an ordinary block following the invocation. Consider the following code that sums up the numbers in an array.
a = [1, 2, 3, 4, 5]
a.inject(0) { |total, n| total + n }
We could be more explicit about using a Proc
object for the block.
a = [1, 2, 3, 4, 5]
summation = Proc.new { |total, n| total + n }
a.inject(0, &summation)
And that code snippet is equivalent to the one above.
One more thing to note. In a method invocation, an &
usually appears before a Proc
object. However, it's allowed before any object that has a to_proc
method. For example Method
object and Symbol
both have to_proc
after Ruby 1.9. So that is why we see code like users.map(&:first_name)
.
Now let's work out exactly how &:first_name
above works.
How does the &:
thing work?
This is where to_proc
method of Symbol
class comes in. So users.map(&:first_name)
translates to users.map { |u| u.first_name }
We can see that that translation is done with a combination of a symbol, the method Symbol#to_proc
, implicit class casting, and &
operator.
Because..
- If we put
&
in front of aProc
instance in the argument position, that will be interpreted as a block. - If we put something other than a
Proc
instance with&
, implicit class casting will try to convert that to aProc
instance usingto_proc
method defined on that object if there is any. In case of aSymbol
there is ato_proc
method.
One last thing. A subtle point to note: all method names are available by their Symbol names as well. You can call a method on an object like 1.to_s and 1.send(:to_s)
. So really (1..10).each(&:to_s)
is equivalent to (1..10).each { |x| x.send(:to_s) }
. The symbol is passed as an argument to the send() method.