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


495

我在RailsCast中找到了以下代码:

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

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


122
顺便说一句,我听说过这种叫“椒盐脆饼冒号”的食物。
Josh Lee 2010年

6
哈哈。我知道这是一个“&”号。我从未听说过它叫“椒盐脆饼”,但这很有道理。
DragonFax

74
称其为“椒盐脆饼冒号”虽然容易引起误解,但却具有误导性。红宝石中没有“&:”。“&”号是一个带有“:”符号的“一元&运算符”。如果有的话,它是“椒盐脆饼符号”。只是说。
fontno

3
tags.map(&:name)是来自tags.map {| s | s.name}
kaushal sharma

3
“椒盐脆饼冒号”听起来像是一种痛苦的疾病……但是我喜欢这个符号的名称:)
zmorris

Answers:


517

这是简写 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

41
这是比我更好的答案。
奥利弗·

91
tags.map(:name.to_proc)本身就是tags.map {| tag | tag.name}
Simone Carletti

5
这不是有效的红宝石代码,您仍然需要&,即tags.map(&:name.to_proc).join(' ')
horseyguy 2011年

5
Symbol#to_proc在C中实现,而不是在Ruby中实现,但这就是在Ruby中的样子。
安德鲁·格林

5
@AndrewGrimm,它首先是使用该代码在Ruby on Rails中添加的。然后在版本1.8.7中将其作为本机红宝石功能添加。
卡梅隆·马丁

174

许多人不知道的另一个很酷的速记是

array.each(&method(:foo))

这是的简写

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

通过调用method(:foo)我们采取了Method对象从self代表其foo方法,以及所使用的&,以表明它有一个to_proc 方法,它转换成一个Proc

当您想做无指向性的样式时,这非常有用。一个示例是检查数组中是否有任何与string相等的字符串"foo"。有常规方法:

["bar", "baz", "foo"].any? { |str| str == "foo" }

还有一种无意义的方式:

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

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


25
array.each{|e| foo(e)}仍然更短:-)反正+1
Jared Beck

您可以使用映射另一个类的构造函数&method吗?
全息原理

3
@finishingmove是的,我猜。试试这个[1,2,3].map(&Array.method(:new))
Gerry


45

同时让我们注意,“&”号#to_proc魔术符可以用于任何类别,而不仅限于Symbol。许多Rubyist选择#to_proc在Array类上进行定义:

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类。并且由于我#to_proc在Array上定义了方法,因此该行变为:

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

这是纯金!
kubak

38

这是简写 tags.map { |tag| tag.name }.join(' ')


不,它在Ruby 1.8.7及更高版本中。
Chuck

它是map的简单用法还是Ruby总是以特定的方式解释'&'?
collimarco

7
@collimarco:正如jleedev在回答中所说,一元运算&符调用to_proc其操作数。因此,它并不特定于map方法,并且实际上适用于采用一个块并将一个或多个参数传递给该块的任何方法。
查克(Chuck)

36
tags.map(&:name)

是相同的

tags.map{|tag| tag.name}

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


1
我一直在寻找答案,而不是专门针对proc(但这是请求者的问题)
matrim_c

好答案!很好地为我澄清了。
apadana

14

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'来给出obj1和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 [1,'a'],例如,:first.to_proc.call([1,'a'])已执行。

  3. Symbol类中的to_proc过程将发送消息发送到[1,'a']带有参数(:first)的数组对象(),例如[1,'a'].send(:first)已执行。

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

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


1
乔希·李(Josh Lee)的答案是绝对正确的,正如您通过思考所看到的那样[1,2,3,4,5,6].inject(&:+)–注入期望具有两个参数(备注和项)的lambda :+.to_proc并将其传递- Proc.new |obj, *args| { obj.send(self, *args) }{ |m, o| m.+(o) }
Uri Agassi 2014年

11

这里发生了两件事,理解这两者很重要。

如其他答案所述,该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为一个。我们可以通过尝试在.map不带“&”号的情况下将proc传递给它来证明这一点:

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它预计该块。


老实说,这是给出的最佳答案。您向您解释&符背后的机制,以及为什么我们会以proc结尾,直到您回答我才明白。谢谢。
Fralcon

5

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

to_proc实际上是用C实现的


5

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

这是

array.map { |element| element.name }

它返回元素(标签)名称的数组


2

尽管我们已经有了不错的答案,但是从初学者的角度来看,我想添加其他信息:

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

这意味着,您要将另一个方法作为参数传递给map函数。(实际上,您传递的是一个已转换为proc的符号。但这在这种情况下并不重要)。

重要的是您有一个method名称name,该名称将由map方法用作参数而不是传统block样式。



1

:name是指向name标记对象方法的符号。当我们传递&:name给时map,它将被name视为proc对象。简而言之,tags.map(&:name)充当:

tags.map do |tag|
  tag.name
end


1

首先,&:name是的快捷方式&:name.to_proc,其中:name.to_proc返回一个Proc(与lambda类似但不相同的东西),当使用对象作为(第一个)参数调用该name对象时,将调用该对象上的方法。

其次,虽然&def foo(&block) ... end转换一个块传递到foo一个Proc,但它确实当应用于相反Proc

因此,&:name.to_proc是一个以对象为参数并name在其上调用方法的块,即{ |o| o.name }


0

如下:

def tag_names
  if @tag_names
    @tag_names
  else
    tags.map{ |t| t.name }.join(' ')
end
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.