0

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?

sawa
  • 165,429
  • 45
  • 277
  • 381
Jay Wang
  • 37
  • 4

2 Answers2

3

& 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.

Jordan Running
  • 102,619
  • 17
  • 182
  • 182
  • it means `&:some_symbol` is directive for the parser but not to the ruby interpreter. Am I correct? – Shiva Jul 07 '16 at 04:23
  • Not really. You can think of `&` like any other operator, just like the `-` in `-9` or the `!` in `!foo`, and `&:some_symbol` as a plain old Ruby expression. The only difference is that you can only use `&` in certain contexts, like the last argument in a method call. You can use `&` on any object that responds to `to_proc` (in Ruby 2.3+ [that includes Hashes](https://bugs.ruby-lang.org/issues/11653)). I only mentioned the parser because you were getting a SyntaxError, and that didn't have anything to do with the `&`; it was only because `:*(2)` isn't valid Ruby. – Jordan Running Jul 07 '16 at 04:45
-1

Ampersand and object (&:method)

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

  1. At each iteration of map, one item of the array (an integer) is passed to &:to_s
  2. The :to_s symbol (which is a reference to the to_s method) is passed to the & operator, which creates a proc that will take an argument (an array item), call to_s on the argument and return the value converted into string;
  3. The map method returns a new array containing the strings "1", "2", "3", "4" and "5".
Community
  • 1
  • 1
BrunoF
  • 3,239
  • 26
  • 39