Rails 1.1 adds a conversion method Symbol#to_proc
that allows specifying a mapping by using a symbol instead of a block. Here's an example:
[1, 2, 3].map(&:to_s)
is equivalent to
[1, 2, 3].map { |i| i.to_s }
and of course can also be written
message = :to_s
[1, 2, 3].map(&message)
Here's what this does.
[1, 2, 3].map(&:to_s) # => ["1", "2", "3"]
My first reaction to seeing this was to wonder what the heck that odd ampersand syntax was. I did some digging, but wasn't able to figure it out entirely...
The Pickaxe book says:
If the last argument to a method is preceded by an ampersand, Ruby assumes that it is a Proc object. It removes it from the parameter list, converts the Proc object into a block, and associates it with the method.
In fact it repeats that in several places throughout the book. But the converse notation or preceding an argument with an ampersand in a method call is only implied in one place. In Chapter 23, "Duck Typing", section "Standard Protocols and Coercions", it describes the to_proc
coercion as "Used to convert an object prefixed with an ampersand in a method call." (Thanks to Chad Fowler for pointing me in the right direction to find that rather obscure reference.)
Discovering that was one of those moments where I really had to sit back and just admire the Ruby language for a moment. The coercion operation is completely polymorphic! Any object preceded by an ampersand in a method call will be converted to a Proc by sending it the to_proc
message. Of course, the object must implement a to_proc
method, but Rails 1.1 adds just that to Symbol.
class Symbol
def to_proc
Proc.new { |obj, *args| obj.send(self, *args) }
end
end
Pretty nifty.
The new syntax for mapping seems to be nicer for chaining calls. Compare:
[1, 2, 3, 4].find_all(&:even?).map(&:to_s)
[1, 2, 3, 4].find_all {|i| i.even?}.map { |i| i.to_s }
I think I like it.
Thank you for having clearly explained this impressive trick :-).
This trick is unfortunately as handy as CPU time inefficient. I made a benchmark: traditional calls (with a block as in
obj.each {|e| e.method_name }
) are way (by 3.4x) faster than using a symbol that will be automatically converted to a Proc (likeobj.each(&:method_name)
).Here are the results of the speed test (you will find the code used to produce these results below them):
Ruby code: