I'm struggling in understanding when to use the ampersand in passing symbols to functions representing a method. For example, If I wanted to calculate the sum of the range 1..10, I could do the following:
(1..10).inject(:+)
This originally lead me to believe that if you wanted to pass a symbol to define a method to "Magically" be used in the function, you would pass the function name as a symbol. But then I see something like this in rails:
total = Product.find(product_list).sum(&:price)
If I understand correctly, &:price is the same as calling :price.to_proc. I don't understand how the above works.
In actual parameter list of a method call, &object
is built-in syntax, which will
- convert
object
to a Proc using object.to_proc
- pass the Proc as the block parameter of the method
Symbol#to_proc
converts the symbol (eg. :the_symbol) to proc {|obj| obj.send(:the_symbol)}
. And you can use this syntax whenever object
responds to to_proc
method and returns a Proc.
abc = "aha~"
class << abc
def to_proc
proc {|obj| obj.to_i * 2 }
end
end
p ["1", "2", "3"].map(&abc)
#=> [2, 4, 6]
(1..10).inject(:+)
shows that inject accepts a symbol as parameter. How the symbol is used is method specific behavior. In inject
's special case, it has the same effect as (1..10).inject{|a, b| a.send(:+, b)}
. It's just a simple, normal parameter, the effect depends on the implementation of the method that accept the symbol as parameter.
Note that sum
in ActiveSupport accepts a block with single parameter, which has the effect "map values in the original sequence to new ones and calculate the sum of them", so
total = Product.find(product_list).sum(&:price)
is equivalent as
total = Product.find(product_list).sum(&proc{|p| p.send(:price)})
# or you may write
total = Product.find(product_list).sum{|p| p.price }
which has the same return value as the following but won't produce intermediate temp array:
total = Product.find(product_list).map{|p| p.price}.sum