I am learning Ruby and was looking at the documentation of Array#map
here, and it says the the syntax is as follows
map {|element| ... } → new_array
map → new_enumerator
But we are able to do arr.map(&:to_s)
to stringify each element of the array.
As far as I understand, &:to_s
is just syntactic sugar for :to_s.to_proc
so (as given here), it means that map accepts a Proc
object as argument. But its method signature says otherwise.
I have a few questions regarding this.
Can someone please explain this behavior and point to the relevant documentation for the same?
What exactly does Proc
mean in this context? Isn't to_s
a method of the underlying class? What does it mean for me to a pass a Proc
object of to_s
which has no information about the underlying class on which it is going to be called.
Any help would be great!
It's not exactly accurate to say "&:to_s
is just syntactic sugar for :to_s.to_proc
", because that glosses over the rest of what the &
character is doing.
You are likely aware that there is a "literal block" syntax in Ruby with two variations (docs):
foo { 1 + 1 }
foo do
1 + 1
end
A Proc
object is an object with the special property that it is allowed to be used in place of the literal block syntax.
So, for any method that accepts a literal block, you can instead pass it a Proc
object—but only by using the &
syntax (docs):
my_proc = Proc.new { 1 + 1 }
foo(&my_proc)
The &
syntax means "use this Proc object in place of this method's block argument, instead of as a regular positional argument."
This is where the "sugar" comes in. If you use the &
syntax to pass a non-Proc
object, Ruby does you the favor of attempting to call to_proc
on that object, to turn it into a Proc
. You could do the same thing yourself, but you don't have to:
# Equivalent:
foo(¬_a_proc.to_proc)
foo(¬_a_proc)
In your example, the object you passed using the &
syntax, :to_s
, is a Symbol
object. Because it is not a Proc
object, Ruby calls to_proc
on it. There is a method Symbol#to_proc
(docs) that turns the symbol :to_s
into a Proc
that is approximately1 equivalent to{ |obj| obj.to_s }
.
The end result:
arr = [1,2,3]
my_proc = Proc.new { |obj| obj.to_s }
# Equivalent:
arr.map { |obj| obj.to_s }
arr.map(&my_proc)
arr.map(&:to_s.to_proc)
arr.map(&:to_s)
1 (For some of the differences that make it only "approximately", see comments below and What's the difference between a proc and a lambda in Ruby? )
As for
But its method signature says otherwise.
the documentation is a little fuzzy on how blocks and Proc
s are represented. There is this section of the Proc
class documentation (docs) that addresses it indirectly:
Creation
There are several methods to create a
Proc
…
Receiving a block of code into proc argument (note the
&
):def make_proc(&block) block end proc3 = make_proc {|x| x**2 }
So, if you were to implement the map
method yourself, the argument signature could look like this:
def map(&block)
But as you can see, in order to show the needed block parameters, Ruby method documentation is instead written as you quoted it:
map {|element| ... } → new_array
and I don't think there's any place in the documentation that explains that exact relationship any better than what's already linked above.