我在 a RailsCast 中找到了这段代码:
def tag_names
@tag_names || tags.map(&:name).join(' ')
end
map(&:name)
中的 (&:name)
是什么意思?
tags.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
对象,并使用 &
表示它具有将其转换为 Proc
的 to_proc
method .
当您想要以 point-free 风格做事时,这非常有用。一个例子是检查数组中是否有任何字符串等于字符串 "foo"
。有常规的方法:
["bar", "baz", "foo"].any? { |str| str == "foo" }
还有一种免点方式:
["bar", "baz", "foo"].any?(&"foo".method(:==))
首选方式应该是最易读的方式。
array.each{|e| foo(e)}
仍然更短:-) 无论如何 +1
&method
映射另一个类的构造函数吗?
[1,2,3].map(&Array.method(:new))
相当于
def tag_names
@tag_names || tags.map { |tag| tag.name }.join(' ')
end
tags.map(&:name)
是相同的
tags.map{|tag| tag.name}
&:name
只是使用符号作为要调用的方法名。
同时让我们注意,& 符号 #to_proc
魔法可以与任何类一起使用,而不仅仅是符号。许多 Rubyist 选择在 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(' ')
的简写
&
运算符在其操作数上调用 to_proc
。所以它并不特定于 map 方法,实际上适用于任何采用块并将一个或多个参数传递给块的方法。
这里发生了两件事,理解这两件事很重要。
如其他答案中所述,正在调用 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
它期望的块。
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 将第一个输入 [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 使用参数 [1,'a'] 向 :first.to_proc 发送调用消息,例如 :first.to_proc.call([1,'a']) 被执行。 Symbol 类中的 to_proc 过程向数组对象 ([1,'a']) 发送发送消息,参数为 (:first),例如 [1,'a'].send(:first) 被执行。迭代 [[1,'a'],[2,'b'],[3,'c']] 对象中的其余元素。
这与执行 [[1,'a'],[2,'b'],[3,'c']].map(|e| e.first)
表达式相同。
[1,2,3,4,5,6].inject(&:+)
所看到的那样 - inject 需要一个带有两个参数(memo 和 item)的 lambda,而 :+.to_proc
提供它 - Proc.new |obj, *args| { obj.send(self, *args) }
或 {4 }
(&:name) 是 (&:name.to_proc) 的缩写,它与 tags.map{ |t| t.name }.join(' ')
相同
to_proc 实际上是用 C 实现的
map(&:name) 接受一个可枚举对象(在您的情况下为标签)并为每个元素/标签运行 name 方法,从该方法输出每个返回值。
这是一个简写
array.map { |element| element.name }
它返回元素(标签)名称的数组
首先,&:name
是 &:name.to_proc
的快捷方式,其中 :name.to_proc
返回一个 Proc
(与 lambda 类似,但不完全相同),当使用对象作为(第一个)参数调用时,调用 { 5} 该对象上的方法。
其次,虽然 def foo(&block) ... end
中的 &
将传递给 foo
的块转换为 Proc
,但它在应用于 Proc
时却相反。
因此,&:name.to_proc
是将对象作为参数并在其上调用 name
方法的块,即 { |o| o.name }
。
尽管我们已经有了很好的答案,但从初学者的角度来看,我想添加其他信息:
Ruby 中的 map(&:name) 是什么意思?
这意味着,您将另一个方法作为参数传递给 map 函数。 (实际上,您传递的是一个符号,该符号被转换为一个过程。但这在这种特殊情况下并不重要)。
重要的是您有一个名为 name
的 method
,map 方法将使用它作为参数,而不是传统的 block
样式。
它基本上对数组中的每个标签执行方法调用 tag.name
。
它是一种简化的 ruby 速记。
这里的 :name
是指向标签对象的方法 name
的符号。当我们将 &:name
传递给 map
时,它会将 name
视为 proc 对象。简而言之,tags.map(&:name)
充当:
tags.map do |tag|
tag.name
end
它的意思是
array.each(&:to_sym.to_proc)
它与以下相同:
def tag_names
if @tag_names
@tag_names
else
tags.map{ |t| t.name }.join(' ')
end
不定期副业成功案例分享
&
,即tags.map(&:name.to_proc).join(' ')
tags.map { |tag| tag.name }
实际上是tags.map(&:name.to_proc)
所做的,但它本身并不完全是简写。这是因为 procs 可以转换为块,使用 &运算符,当它们被传递给使用 yield 的方法时,因此需要一个块。 (请参阅 Ruby 文档 here)。正如 Josh Lee 在上面的帖子中所展示的,符号也可以转换为 procs,然后可以从那里转换为块,这是必要的,因为 map 使用块。