Why does:
[1,2,3,4,5].map(&:to_s) #=> ["1", "2", "3", "4", "5"]
work but:
[1,2,3,4,5].map(&:*(2))
throws an unexpected syntax error?
&
is called the to_proc
operator. It calls the to_proc
method on the expression that follows it and then passes the resulting Proc to the method as a block.
In the case of &:to_s
, :to_s
is a Symbol, so it the operator calls Symbol#to_proc
. The docs are a little garbled, but suffice it to say that these two expressions are more-or-less equivalent:
my_proc = :to_s.to_proc
my_proc = Proc.new {|obj| obj.to_s }
So the answer to the question "Why doesn't &:*(2)
work?" is that the expression that follows the &
operator, :*(2)
, isn't a valid Ruby expression. It makes about as much sense to the Ruby parser as "hello"(2)
.
There is, by the way, a way to do what you're trying to do:
[1,2,3,4,5].map(&2.method(:*))
# => [2, 4, 6, 8, 10]
In the above code, 2.method(:*)
returns a reference to the *
method of the object 2
as a Method object. Method objects behave a lot like Proc objects, and they respond to to_proc
. However, the above isn't exactly equivalent—it does 2 * n
rather than n * 2
(a distinction that doesn't matter if n
is also a Numeric)—and it's not any more succinct or readable than {|n| n * 2 }
, and so rarely worth the trouble.
The & operator can also be used to pass an object as a block to a method, as in the following example:
arr = [ 1, 2, 3, 4, 5 ]
arr.map { |n| n.to_s }
arr.map &:to_s
Both the examples above have the same result. In both, the map method takes the arr array and a block, then it runs the block on each element of the array. The code inside the block runs to_s on each element, converting it from integers to strings. Then, the map method returns a new array containing the converted items.
The first example is common and widely used. The second example may look a bit cryptic at first glance. Let's see what's happening:
In Ruby, items prefixed with colon (:) are symbols. If you are not familiar with the Symbol class/data type, I suggest you Google it and read a couple of articles before continuing. All method names in Ruby are internally stored as symbols. By prefixing a method name with a colon, we are not converting the method into a symbol, neither are we calling the method, we are just passing the name of the method around (referencing the method). In the example above, we are passing :to_s, which is a reference to the to_s method, to the ampersand (&) operator, which will create a proc (by calling to_proc under the hood). The proc takes a value as an argument, calls to_s on it and returns the value converted into a string.
Although the :to_s symbol is always the same, when running the map loop, it will refer to the to_s method of the class corresponding to each array item. If we passed an array such as [ 21, 4.453, :foobar, ] to the map method, the to_s method of the Fixnum class would be applied (called) on the first item, the to_s method of the Float class would be applied to the second item and the to_s method of the Symbol class would be applied to the third item. This makes sense because we are not passing the actual to_s method to the ampersand operator, just its name.
Below is an example of creating a proc that takes an argument, calls a method on it and returns the result of the method.
p = :upcase.to_proc
p.call("foo bar")
Output:
=> "FOO BAR"
Let's review what is going on in arr.map &:to_s