What does map(&:name) mean in Ruby?
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?
It's shorthand for tags.map(&:name.to_proc).join(' ')
If foo
is an object with a to_proc
method, then you can pass it to a method as &foo
, which will call foo.to_proc
and use that as the method's block.
The Symbol#to_proc
method was originally added by ActiveSupport but has been integrated into Ruby 1.8.7. This is its implementation:
class Symbol
def to_proc
Proc.new do |obj, *args|
obj.send self, *args
end
end
end
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.
It's equivalent to
def tag_names
@tag_names || tags.map { |tag| tag.name }.join(' ')
end
tags.map(&:name)
is The same as
tags.map{|tag| tag.name}
&:name
just uses the symbol as the method name to be called.
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 shorthand for tags.map { |tag| tag.name }.join(' ')
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.
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.
(&:name) is short for (&:name.to_proc) it is same as tags.map{ |t| t.name }.join(' ')
to_proc is actually implemented in C
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
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 }
.
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.
It basically execute the method call tag.name
on each tags in the array.
It is a simplified ruby shorthand.
There isn't a &: operator in Ruby. What you are seeing is the & operator applied to a :symbol.
In a method argument list, the & operator takes its operand, converts it to a Proc object if it isn't already (by calling to_proc on it) and passes it to the method as if a block had been used.
my_proc = Proc.new { puts "foo" }
my_method_call(&my_proc) # is identical to: my_method_call { puts "foo" }
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
it means
array.each(&:to_sym.to_proc)
It is same as below:
def tag_names
if @tag_names
@tag_names
else
tags.map{ |t| t.name }.join(' ')
end
精彩评论