I found this code in a RailsCast:
def tag_names
@tag_names || tags.map(&:name).join(' ')
end
What does the (&:name)
in map(&:name)
mean?
This question is related to
ruby
syntax
operators
parameter-passing
While let us also note that ampersand #to_proc
magic can work with any class, not just Symbol. Many Rubyists choose to define #to_proc
on Array class:
class Array
def to_proc
proc { |receiver| receiver.send *self }
end
end
# And then...
[ 'Hello', 'Goodbye' ].map &[ :+, ' world!' ]
#=> ["Hello world!", "Goodbye world!"]
Ampersand &
works by sending to_proc
message on its operand, which, in the above code, is of Array class. And since I defined #to_proc
method on Array, the line becomes:
[ 'Hello', 'Goodbye' ].map { |receiver| receiver.send( :+, ' world!' ) }
It's equivalent to
def tag_names
@tag_names || tags.map { |tag| tag.name }.join(' ')
end
Although we have great answers already, looking through a perspective of a beginner I'd like to add the additional information:
What does map(&:name) mean in Ruby?
This means, that you are passing another method as parameter to the map function. (In reality you're passing a symbol that gets converted into a proc. But this isn't that important in this particular case).
What is important is that you have a method
named name
that will be used by the map method as an argument instead of the traditional block
style.
map(&:name) takes an enumerable object (tags in your case) and runs the name method for each element/tag, outputting each returned value from the method.
It is a shorthand for
array.map { |element| element.name }
which returns the array of element(tag) names
Another cool shorthand, unknown to many, is
array.each(&method(:foo))
which is a shorthand for
array.each { |element| foo(element) }
By calling method(:foo)
we took a Method
object from self
that represents its foo
method, and used the &
to signify that it has a to_proc
method that converts it into a Proc
.
This is very useful when you want to do things point-free style. An example is to check if there is any string in an array that is equal to the string "foo"
. There is the conventional way:
["bar", "baz", "foo"].any? { |str| str == "foo" }
And there is the point-free way:
["bar", "baz", "foo"].any?(&"foo".method(:==))
The preferred way should be the most readable one.
(&:name) is short for (&:name.to_proc) it is same as tags.map{ |t| t.name }.join(' ')
to_proc is actually implemented in C
First, &:name
is a shortcut for &:name.to_proc
, where :name.to_proc
returns a Proc
(something that is similar, but not identical to a lambda) that when called with an object as (first) argument, calls the name
method on that object.
Second, while &
in def foo(&block) ... end
converts a block passed to foo
to a Proc
, it does the opposite when applied to a Proc
.
Thus, &:name.to_proc
is a block that takes an object as argument and calls the name
method on it, i. e. { |o| o.name }
.
It basically execute the method call tag.name
on each tags in the array.
It is a simplified ruby shorthand.
Josh Lee's answer is almost correct except that the equivalent Ruby code should have been as follows.
class Symbol
def to_proc
Proc.new do |receiver|
receiver.send self
end
end
end
not
class Symbol
def to_proc
Proc.new do |obj, *args|
obj.send self, *args
end
end
end
With this code, when print [[1,'a'],[2,'b'],[3,'c']].map(&:first)
is executed, Ruby splits the first input [1,'a']
into 1 and 'a' to give obj
1 and args*
'a' to cause an error as Fixnum object 1 does not have the method self (which is :first).
When [[1,'a'],[2,'b'],[3,'c']].map(&:first)
is executed;
:first
is a Symbol object, so when &:first
is given to a map method as a parameter, Symbol#to_proc is invoked.
map sends call message to :first.to_proc with parameter [1,'a']
, e.g., :first.to_proc.call([1,'a'])
is executed.
to_proc procedure in Symbol class sends a send message to an array object ([1,'a']
) with parameter (:first), e.g., [1,'a'].send(:first)
is executed.
iterates over the rest of the elements in [[1,'a'],[2,'b'],[3,'c']]
object.
This is the same as executing [[1,'a'],[2,'b'],[3,'c']].map(|e| e.first)
expression.
it means
array.each(&:to_sym.to_proc)
tags.map(&:name)
is The same as
tags.map{|tag| tag.name}
&:name
just uses the symbol as the method name to be called.
It's shorthand for tags.map { |tag| tag.name }.join(' ')
Here :name
is the symbol which point to the method name
of tag object.
When we pass &:name
to map
, it will treat name
as a proc object.
For short, tags.map(&:name)
acts as:
tags.map do |tag|
tag.name
end
Two things are happening here, and it's important to understand both.
As described in other answers, the Symbol#to_proc
method is being called.
But the reason to_proc
is being called on the symbol is because it's being passed to map
as a block argument. Placing &
in front of an argument in a method call causes it to be passed this way. This is true for any Ruby method, not just map
with symbols.
def some_method(*args, &block)
puts "args: #{args.inspect}"
puts "block: #{block.inspect}"
end
some_method(:whatever)
# args: [:whatever]
# block: nil
some_method(&:whatever)
# args: []
# block: #<Proc:0x007fd23d010da8>
some_method(&"whatever")
# TypeError: wrong argument type String (expected Proc)
# (String doesn't respond to #to_proc)
The Symbol
gets converted to a Proc
because it's passed in as a block. We can show this by trying to pass a proc to .map
without the ampersand:
arr = %w(apple banana)
reverse_upcase = proc { |i| i.reverse.upcase }
reverse_upcase.is_a?(Proc)
=> true
arr.map(reverse_upcase)
# ArgumentError: wrong number of arguments (1 for 0)
# (map expects 0 positional arguments and one block argument)
arr.map(&reverse_upcase)
=> ["ELPPA", "ANANAB"]
Even though it doesn't need to be converted, the method won't know how to use it because it expects a block argument. Passing it with &
gives .map
the block it expects.
It is same as below:
def tag_names
if @tag_names
@tag_names
else
tags.map{ |t| t.name }.join(' ')
end
Source: Stackoverflow.com