关于语法:map(&:name)在Ruby中意味着什么?

What does map(&:name) mean in Ruby?

我在RailsCast中找到了这段代码:

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

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


它是tags.map(&:name.to_proc).join(' ')的简写

如果foo是具有to_proc方法的对象,则可以将其传递给&foo的方法,该方法将调用foo.to_proc并将其用作方法的块。

Symbol#to_proc方法最初由ActiveSupport添加,但已集成到Ruby 1.8.7中。这是它的实现:

1
2
3
4
5
6
7
class Symbol
  def to_proc
    Proc.new do |obj, *args|
      obj.send self, *args
    end
  end
end


另一个很酷的简写,很多人都不知道

1
array.each(&method(:foo))

这是一个简写

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

通过调用method(:foo),我们从self获取了一个表示其foo方法的Method对象,并使用&表示它有一个to_proc方法,将其转换为Proc

当你想做无点风格的事情时,这非常有用。一个示例是检查数组中是否有任何字符串等于字符串"foo"。有传统方式:

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

并且有无点的方式:

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

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


它相当于

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

虽然我们还要注意,&符号#to_proc魔法可以适用于任何类,而不仅仅是符号。许多Rubyist选择在Array类上定义#to_proc

1
2
3
4
5
6
7
8
9
10
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方法,因此该行变为:

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

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


1
tags.map(&:name)

是相同的

1
tags.map{|tag| tag.name}

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


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

1
2
3
4
5
6
7
class Symbol
  def to_proc
    Proc.new do |receiver|
      receiver.send self
    end
  end
end

1
2
3
4
5
6
7
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没有方法自我(这是:第一)。

执行[[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)表达式相同。


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

    如其他答案中所述,正在调用Symbol#to_proc方法。

    但是在符号上调用to_proc的原因是因为它作为块参数传递给map。在方法调用中将&放在参数前面会导致它以这种方式传递。对于任何Ruby方法都是如此,而不仅仅是带符号的map

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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而不使用&符号来显示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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它所期望的块。


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

    to_proc实际上是用C实现的


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

    这是一个简写

    1
    array.map { |element| element.name }

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


    虽然我们已经有了很好的答案,但从初学者的角度来看,我想添加其他信息:

    What does map(&:name) mean in Ruby?

    这意味着,您将另一个方法作为参数传递给map函数。
    (实际上你传递的是一个符号,它被转换成一个过程。但在这个特殊情况下,这并不重要)。

    重要的是你有一个名为namemethod,它将被map方法用作参数而不是传统的block样式。


    它基本上对数组中的每个标签执行方法调用tag.name

    这是一个简化的红宝石简写。


    它的意思是

    1
    array.each(&:to_sym.to_proc)

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

    1
    2
    3
    tags.map do |tag|
      tag.name
    end

    它如下:

    1
    2
    3
    4
    5
    6
    def tag_names
      if @tag_names
        @tag_names
      else
        tags.map{ |t| t.name }.join(' ')
    end