How does the ampersand in `map(&:name)` work?
The shorthand syntax for mapping over a collection and calling a single method on each item is:
people.map(&:name)
But what does the &
do?
It calls to_proc
on the given variable, in this case a Symbol, :name
.
:name.to_proc # => Proc
A proc is an object which wraps an block (function) making it callable.
:name.to_proc.call(person) # => 'Kris'
The proc returned by to_proc
is passed to #map
.
I think Symbol#to_proc
, being part of the core library, is implemented in the interpreter itself as C code (or
Java in the case of JRuby), but if it where implemented in Ruby code it might look something like:
class Symbol
def to_proc
Proc.new { |item| item.public_send(self) }
end
end
The above to_proc
method returns a proc which accepts a single argument and calls #public_send
on it passing in itself the symbol, :name
, as the argument.
When used with map it is the equivalent to:
people.map { |item| item.public_send(:name) }
Which is essentially the same as the longer way of writing the original code:
people.map { |it| it.name }
Any object that has a to_proc
method can be referenced after &
in method
arguments.
String
does not define to_proc
which is why people.map(&"name")
fails.
You actually get a TypeError
not an NoMethodError
.
Method
defines to_proc
which is why you can use methods too after an &
as the argument to #map
and similar methods.
def add_one(num)
num + 1
end
method(:add_one) # => Method
method(:add_one).to_proc # => Proc
method(:add_one).to_proc.call(1) # => 2
[1,2,3].map(&method(:add_one)) # => [2,3,4]
Method#to_proc
as Ruby would potentially be something like:
class Method
def to_proc
Proc.new { |item| self.call(item) }
end
end
This is why you can also put a proc after &
because Proc#to_proc
simply returns itself.
to_s = Proc.new { |item| item.to_s }
[1,2,3].map(&to_s) # => ['1', '2', '3']
In ruby it would simply be:
class Proc
def to_proc
self
end
end
I think this goes to show just how well Ruby exploits both object and functional concepts.
Some final notes:
proc
is a method on Kernel
, thus avalible everwhere, and is
shorthand for Proc.new
.
proc {}.class # => Proc
A lambda is a type of Proc:
lambda {}.class # => Proc
-> {}.class # => Proc
Procs can be called in different ways:
adder = proc { |i| i + 1 }
adder.call(1) # => 2
adder.(1) # => 2
adder[1] # => 2
adder === 1 # => 2
The final ===
is kinda strange, this is so procs can be used in case
statements. It is only useful, in a case, if the proc returns true or false.
hot = proc { |n| n > 20 }
cold = proc { |n| n < 0 }
case temperature
when hot
# ...
when cold
# ...
else
# ...
end
Now we are off topic, so time to finish up.