Книга: Metaprogramming Ruby 2
Назад: Self Yield
Дальше: Appendix 2: Domain-Specific Languages

Symbol#to_proc()

This exotic spell is popular among black-belt Ruby programmers. When I stumbled upon this spell for the first time, I had trouble understanding the reasoning behind it. It’s easier to get there by taking one small step at a time.

Look at this code:

 
names = [​'bob'​, ​'bill'​, ​'heather'​]
 
names.map {|name| name.capitalize } ​# => ["Bob", "Bill", "Heather"]

Focus on the block—a simple “one-call block” that takes a single argument and calls a single method on that argument. One-call blocks are very common in Ruby, especially (but not exclusively) when you’re dealing with arrays.

In a language such as Ruby, which prides itself on being succinct and to the point, even a one-call block looks verbose. Why do you have to go through the trouble of creating a block, with curly braces and all, just to ask Ruby to call a method? The idea of Symbol#to_proc is that you can replace a one-call block with a shorter construct. Let’s start with the smallest piece of information you need, which is the name of the method that you want to call, as a symbol:

 
:capitalize

You want to convert the symbol to a one-call block like this:

 
{|x| x.capitalize }

As a first step, you can add a method to the Symbol class, which converts the symbol to a Proc object:

 
class​ Symbol
*
def​ to_proc
*
Proc.new {|x| x.send(self) }
*
end
 
end

See how this method works? If you call it on, say, the :capitalize symbol, it returns a proc that takes an argument and calls capitalize on the argument. Now you can use to_proc and the & operator to convert a symbol to a Proc and then to a block:

 
names = [​'bob'​, ​'bill'​, ​'heather'​]
*
names.map(&:capitalize.to_proc) ​# => ["Bob", "Bill", "Heather"]

You can make this code even shorter. As it turns out, you can apply the & operator to any object, and it will take care of converting that object to a Proc by calling to_proc. (You didn’t think we picked the name of the to_proc method randomly, did you?) So, you can simply write the following:

 
names = [​'bob'​, ​'bill'​, ​'heather'​]
*
names.map(&:capitalize) ​# => ["Bob", "Bill", "Heather"]

That’s the trick known as Spell: . Neat, huh?

The good news is that you don’t have to write Symbol#to_proc, because it’s already provided by Ruby. In fact, Ruby’s implementation of Symbol#to_proc also supports blocks with more than one argument, which are required by methods such as inject:

 
# without Symbol#to_proc:
 
[1, 2, 5].inject(0) {|memo, obj| memo + obj } ​# => 8
 
 
# with Symbol#to_proc:
 
[1, 2, 5].inject(0, &:+) ​# => 8
 
 
# cool!
Назад: Self Yield
Дальше: Appendix 2: Domain-Specific Languages