Difference between passing &:method and :method as

2019-08-07 08:31发布

问题:

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.

回答1:

In actual parameter list of a method call, &object is built-in syntax, which will

  1. convert object to a Proc using object.to_proc
  2. 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