How &: Works in Ruby
Chances are you've seen and written code like users.map(&:first_name)
in ruby or rails applications many times. And you may have wondered is that &
an operator, is that :
part of a Symbol. It's a strange syntax.
Let's demysify &:
so you'll know how to use it and exactly why 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.
If you like short explanations of common concepts, come back here often. We plan to publish these regularly in the coming months. Or if you prefer to be notified, subscribe via RSS or drop your email below. I will email a round up of posts once or twice a month.