在Ruby中map(&:name)是什么意思?
我在RailsCast中发现了这个代码:
def tag_names @tag_names || tags.map(&:name).join(' ') end
map(&:name)
是什么意思?
这是tags.map(&:name.to_proc).join(' ')
的缩写
如果foo
是带有to_proc
方法的对象,那么可以将它作为&foo
传递给一个方法,该方法将调用foo.to_proc
并将其用作方法的块。
Symbol#to_proc
方法最初由ActiveSupport添加,但已被集成到Ruby 1.8.7中。 这是它的实现:
class Symbol def to_proc Proc.new do |obj, *args| obj.send self, *args end end end
另一个很酷的速记,很多人都不知道
array.each(&method(:foo))
这是一个简写
array.each { |element| foo(element) }
通过调用method(:foo)
我们从self
了一个表示它的foo
方法的Method
对象,并使用&
表示它有一个to_proc
方法将它转换为Proc
。
这是非常有用的,当你想做点事情的自由风格。 一个例子是检查数组中是否有任何string等于string"foo"
。 有传统的方法:
["bar", "baz", "foo"].any? { |str| str == "foo" }
而且有一个无点的方法:
["bar", "baz", "foo"].any?(&"foo".method(:==))
首选的方式应该是最可读的。
这相当于
def tag_names @tag_names || tags.map { |tag| tag.name }.join(' ') end
而让我们也注意到&符号#to_proc
魔法可以与任何类一起工作,而不仅仅是符号。 许多#to_proc
select在Array类上定义#to_proc
:
class Array def to_proc proc { |receiver| receiver.send *self } end end # And then... [ 'Hello', 'Goodbye' ].map &[ :+, ' world!' ] #=> ["Hello world!", "Goodbye world!"]
&
符号&
通过在其操作数上发送to_proc
消息来工作,在上面的代码中,它是Array类的。 而且由于我在Array上定义了#to_proc
方法,该行变成:
[ 'Hello', 'Goodbye' ].map { |receiver| receiver.send( :+, ' world!' ) }
这是tags.map { |tag| tag.name }.join(' ')
的缩写 tags.map { |tag| tag.name }.join(' ')
Josh Lee的回答几乎是正确的,只是等价的Ruby代码应该如下所示。
class Symbol def to_proc Proc.new do |receiver| receiver.send self end end end
不
class Symbol def to_proc Proc.new do |obj, *args| obj.send self, *args end end end
使用这段代码,当print [[1,'a'],[2,'b'],[3,'c']].map(&:first)
被执行时,Ruby将第一个input[1,'a']
转换为1,'a'给出obj
1和args*
'a',因为Fixnum对象1没有方法self(即:first),导致错误。
当[[1,'a'],[2,'b'],[3,'c']].map(&:first)
-
:first
是一个Symbol对象,所以当&:first
被赋予一个map方法作为参数时,Symbol#to_proc被调用。 -
map将呼叫消息发送到:first.to_proc,参数为
[1,'a']
,例如:first.to_proc.call([1,'a'])
。 -
Symbol类中的to_proc过程使用参数(:first)向数组对象(
[1,'a']
)发送发送消息,例如[1,'a'].send(:first)
-
迭代
[[1,'a'],[2,'b'],[3,'c']]
对象中的其余元素。
这与执行[[1,'a'],[2,'b'],[3,'c']].map(|e| e.first)
expression式相同。
tags.map(&:name)
是相同的
tags.map{|tag| tag.name}
&:name
只是使用符号作为要调用的方法名称。
这里发生了两件事情,重要的是理解两者。
如其他答案中所述,正在调用Symbol#to_proc
方法。
但是,在符号上调用to_proc
的原因是因为它被map
为块参数。 在一个方法调用中放置一个参数前面会导致它以这种方式传递。 任何Ruby方法都是如此,而不仅仅是用符号map
。
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)
Symbol
被转换为一个Proc
因为它是作为一个块传入的。 我们可以通过尝试将proc传递给.map
而不显示&符号来显示:
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"]
即使不需要转换,该方法也不知道如何使用它,因为它需要一个块参数。 通过&
赋予.map
它所期望的块。
(&:name)是(&:name.to_proc)的缩写,与tags.map{ |t| t.name }.join(' ')
tags.map{ |t| t.name }.join(' ')
to_proc实际上是在C中实现的
它与以下相同:
def tag_names if @tag_names @tag_names else tags.map{ |t| t.name }.join(' ') end
这里:name
是指向标签对象的方法name
的符号。 当我们将&:name
传递给map
,它将把name
作为一个proc对象。 简而言之, tags.map(&:name)
作用如下:
tags.map do |tag| tag.name end
它的意思是
array.each(&:to_sym.to_proc)