在Ruby中,map(&:name)是什么意思?

551

我在RailsCast中发现了这段代码:

def tag_names
  @tag_names || tags.map(&:name).join(' ')
end

map(&:name) 中的 (&:name) 是什么意思?


139
顺便说一下,我听说这个被称为“椒盐脆饼结肠”。 - Josh Lee
7
哈哈,我知道它是“和符号”(Ampersand)。我从来没听说过它被称为“椒盐脆饼干”(pretzel),但这也有道理。 - DragonFax
1
此外,您可以删除括号tags.map &:name以获得更短的条目。 - itsnikolay
81
称其为“椒盐脾结”是误导性的,尽管这个词很吸引人。在 Ruby 中没有 "&:" 这个符号。符号 & 是一个“一元与符号运算符”,后面跟着一个压缩在一起的 : 符号,如有必要,它可以称为“椒盐符号”。就这样说。 - fontno
4
"tags.map(&:name)" 是 "tags.map{|s| s.name}" 的简写形式,它们的意思相同。 - kaushal sharma
https://www.brianstorti.com/understanding-ruby-idiom-map-with-symbol/ - jgomo3
17个回答

548

这是对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

104
tags.map(:name.to_proc) 是 tags.map { |tag| tag.name } 的简写形式。在这个简写形式中,使用了 :name.to_proc 方法来将名称转换为一个 Proc 对象,再将其传递给 map 方法以代替块参数 {|tag| tag.name}。最终的效果与原始代码相同,都是对标签数组中的每个标签提取出其名称,并返回一个新的数组。 - Simone Carletti
6
这不是有效的 Ruby 代码,你仍需要使用 & 符号,即 tags.map(&:name.to_proc).join(' ') - horseyguy
5
Symbol#to_proc 的实现是在 C 语言中完成的,而不是 Ruby,但这就是它在 Ruby 中看起来的样子。 - Andrew Grimm
5
@AndrewGrimm是指该代码最初在Ruby on Rails中被添加。随后在1.8.7版本中作为Ruby的本地功能被添加进去。 - Cameron Martin
4
虽然 tags.map { |tag| tag.name }tags.map(&:name.to_proc) 效果相同,但前者并不是简写。这是因为当一个方法需要一个块时(参考 Ruby 文档 此处),procs 可以使用 & 运算符转换成块。正如 Josh Lee 在上面的帖子中所示,符号也可以转换为 procs ,然后再转换为块,这是必要的,因为 map 使用的就是块。 - jazzyfresh
显示剩余10条评论

199

还有一个很酷的简写方式,不为人知,它是

array.each(&method(:foo))

这是一个简写形式,表示

array.each { |element| foo(element) }

通过调用method(:foo),我们从self中取出表示其foo方法的Method对象,并使用&表示它有一个to_proc method,将其转换为Proc
当您想以无参数风格做事情时,这非常有用。例如,检查数组中是否有任何字符串等于字符串"foo"。 有常规方式:
["bar", "baz", "foo"].any? { |str| str == "foo" }

还有一种无点方式:

["bar", "baz", "foo"].any?(&"foo".method(:==))

首选方式应该是最易读的方式。

31
array.each{|e| foo(e)}仍然更短 :-) 无论如何+1 - Jared Beck
你能使用 &method 映射另一个类的构造函数吗? - holographic-principle
4
@finishingmove 好的,我猜你想试试这个 [1,2,3].map(&Array.method(:new)) - Gerry
剧透:结果是[[nil],[nil,nil],[nil,nil,nil]] - 这可能会很有用。 - Cadoiz

84

它等价于

def tag_names
  @tag_names || tags.map { |tag| tag.name }.join(' ')
end

69
tags.map(&:name)

就是

tags.map{|tag| tag.name}

&:name只是使用该符号作为要调用的方法名称。


1
我可以使用多个值来完成这个操作吗,比如 tags.map(&:name, &:id) - Qasim
1
@Qasim,你不能这样做,但你可以这样做:tags.map { |t| t.name.to_s << t.age.to_s } - Albert.Qing

51

请注意,符号“&”的#to_proc魔法可以与任何类一起使用,而不仅仅是符号类。许多Ruby程序员选择在数组类上定义#to_proc

class Array
  def to_proc
    proc { |receiver| receiver.send *self }
  end
end

# And then...

[ 'Hello', 'Goodbye' ].map &[ :+, ' world!' ]
#=> ["Hello world!", "Goodbye world!"]

Ampersand符号&通过在其操作数上发送to_proc消息来工作,在上面的代码中,其操作数是Array类的实例。由于我在Array上定义了#to_proc方法,因此该行变为:

[ 'Hello', 'Goodbye' ].map { |receiver| receiver.send( :+, ' world!' ) }

41
这是一个简写形式,意思是将tags.map { |tag| tag.name }.join(' ')中的所有标签名字用空格连接起来。

不是的,它是在Ruby 1.8.7及以上版本中。 - Chuck
这是一个简单的map习惯用法,还是Ruby总是以特定方式解释'&'? - collimarco
7
正如jleedev在他的答案中所说,一元运算符&会对其操作数调用to_proc方法。因此,它不仅适用于map方法,实际上可以用于任何需要将一个或多个参数传递给块的方法。 - Chuck

18
这里发生了两件事情,了解两者都非常重要。
正如其他答案所述,调用了`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 可以提供所需的块。

15

除了等价的Ruby代码应该如下所示,Josh Lee的答案几乎是正确的。

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)时:

  1. :first是一个Symbol对象,因此当将&:first作为参数提供给map方法时,会调用Symbol#to_proc。

  2. map向:first.to_proc发送call消息并传递参数[1,'a'],例如执行:first.to_proc.call([1,'a'])

  3. Symbol类中的to_proc过程向一个数组对象([1,'a'])发送send消息,并传递参数(:first),例如执行[1,'a'].send(:first)

  4. 迭代[[1,'a'],[2,'b'],[3,'c']]对象的其余元素。

这与执行表达式[[1,'a'],[2,'b'],[3,'c']].map(|e| e.first)相同。


2
Josh Lee的回答是_绝对_正确的,你可以通过思考[1,2,3,4,5,6].inject(&:+)来看出来 - inject期望一个带有两个参数(memo和item)的lambda表达式,而:+.to_proc提供了它 - Proc.new |obj, *args| { obj.send(self, *args) }{ |m, o| m.+(o) } - Uri Agassi

5

map(&:name)将一个可枚举对象(在您的情况下为标签)作为输入,并针对每个元素/标签运行name方法,输出从该方法返回的值。

这是一种简写形式:

array.map { |element| element.name }

该函数返回元素(标记)名称的数组。


5

(&:name) 是 (&:name.to_proc) 的简写,它与 tags.map{ |t| t.name }.join(' ') 相同。

to_proc 实际上是用 C 实现的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接