我在一个RailsCast中找到了这段代码:
def tag_names @tag_names || tags.map(&:name).join(' ') end
map(&:name)中的(&:name)是什么意思?
map(&:name)
(&:name)
它是tags.map { |tag| tag.name }.join(' ')的简写
tags.map { |tag| tag.name }.join(' ')
它等价于
def tag_names @tag_names || tags.map { |tag| tag.name }.join(' ') end
它是tags.map(&:name.to_proc).join(' ')的简写
tags.map(&:name.to_proc).join(' ')
如果foo是一个带有to_proc方法的对象,那么您可以将它传递给一个名为&foo的方法,该方法将调用foo.to_proc并将其用作方法的块。
foo
to_proc
&foo
foo.to_proc
Symbol#to_proc方法最初是由ActiveSupport添加的,但已经集成到Ruby 1.8.7中。这是它的实现:
Symbol#to_proc
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中获取了一个Method对象,它表示它的foo方法,并使用&表示它有一个to_proc 方法对象,将它转换为Proc。
method(:foo)
self
Method
&
Proc
这是非常有用的,当你想做点免费风格的事情。一个例子是检查数组中是否有字符串等于字符串"foo"。有一种传统的方式:
"foo"
["bar", "baz", "foo"].any? { |str| str == "foo" }
还有一种简单的方法:
["bar", "baz", "foo"].any?(&"foo".method(:==))
首选的方式应该是最易读的方式。
同时让我们也注意到& #to_proc魔法可以用于任何类,而不仅仅是符号。很多ruby会选择在Array类上定义#to_proc:
#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!' ) }
Josh Lee的回答几乎是正确的,除了等价的Ruby代码应该如下所示。
class Symbol def to_proc Proc.new do |receiver| receiver.send self end end end
不
使用这段代码,当执行print [[1,'a'],[2,'b'],[3,'c']].map(&:first)时,Ruby将第一个输入[1,'a']分割为1和'a',从而给出obj 1和args* 'a',从而导致错误,因为Fixnum对象1没有方法self(即:first)。
print [[1,'a'],[2,'b'],[3,'c']].map(&:first)
[1,'a']
obj
args*
当执行[[1,'a'],[2,'b'],[3,'c']].map(&:first)时;
[[1,'a'],[2,'b'],[3,'c']].map(&:first)
:first是一个Symbol对象,因此当&:first作为参数被赋予一个映射方法时,Symbol#to_proc将被调用。
:first
&:first
地图将呼叫信息发送到:first。执行参数为[1,'a']的to_proc,例如:first.to_proc.call([1,'a'])。
:first.to_proc.call([1,'a'])
符号类中的to_proc过程向数组对象([1,'a'])发送一个发送消息,参数为(:first),例如,[1,'a'].send(:first)被执行。
[1,'a'].send(:first)
遍历[[1,'a'],[2,'b'],[3,'c']]对象中的其余元素。
[[1,'a'],[2,'b'],[3,'c']]
这与执行[[1,'a'],[2,'b'],[3,'c']].map(|e| e.first)表达式相同。
[[1,'a'],[2,'b'],[3,'c']].map(|e| e.first)
这里发生了两件事,理解这两件事很重要。
正如在其他回答中所描述的,正在调用Symbol#to_proc方法。
但是在符号上调用to_proc的原因是因为它被作为块参数传递给map。将&放在方法调用中的参数前面会导致它以这种方式传递。这适用于任何Ruby方法,而不仅仅是带符号的map。
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,因为它是作为块传入的。我们可以通过尝试传递一个不带&号的进程给.map来显示这一点:
Symbol
.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它所期望的块。
如下所示:
def tag_names if @tag_names @tag_names else tags.map{ |t| t.name }.join(' ') end
:name
name
&:name
tags.map(&:name)
tags.map do |tag| tag.name end
(&:name)是(&:name.to_proc)的缩写,与tags.map{ |t| t.name }.join(' ')相同
tags.map{ |t| t.name }.join(' ')
to_proc实际上是用C语言实现的
是一样的
tags.map{|tag| tag.name}
&:name只是使用符号作为要调用的方法名。
它的意思是
array.each(&:to_sym.to_proc)
虽然我们已经有了很好的答案,但从初学者的角度来看,我想添加额外的信息:
map(&:name)在Ruby中是什么意思?
重要的是,您有一个名为name的method,它将被map方法用作参数,而不是传统的block样式。
method
block
地图(及名称):接受一个可枚举对象(在您的例子中是标签),并为每个元素/标签运行name方法,输出该方法的每个返回值。
array.map { |element| element.name }
返回元素(标签)名称的数组
它基本上在数组中的每个标记上执行方法调用tag.name。
tag.name
它是一个简化的ruby简写。
首先,&:name是&:name.to_proc的快捷方式,其中:name.to_proc返回Proc(类似于lambda,但不完全相同),当以对象作为(第一个)参数调用时,将调用该对象上的name方法。
&:name.to_proc
:name.to_proc
其次,虽然def foo(&block) ... end中的&将传递给foo的块转换为Proc,但应用到Proc时则相反。
def foo(&block) ... end
因此,&:name.to_proc是一个以对象为参数并在其上调用name方法的块,即{ |o| o.name }。
{ |o| o.name }
Ruby中没有&:运算符。你所看到的是&运算符应用于:符号。
在方法参数列表中,&operator获取其操作数,如果还没有将其转换为Proc对象(通过对其调用to_proc),并将其传递给方法,就像使用了块一样。
my_proc = Proc.new {put " food "}
my_method_call(&my_proc) #与: My_method_call {put " food "} < / p >