Что означает карта (&: name) в Ruby? - PullRequest
477 голосов
/ 01 августа 2009

Я нашел этот код в RailsCast :

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

Что означает (&:name) в map(&:name)?

Ответы [ 15 ]

503 голосов
/ 01 августа 2009

Это сокращение для 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
166 голосов
/ 08 марта 2012

Другая классная стенография, неизвестная многим, -

array.each(&method(:foo))

, что является сокращением для

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

Вызвав method(:foo), мы взяли Method объект из self, который представляет его метод foo, и использовали &, чтобы показать, что он имеет to_proc метод , который преобразует его в Proc.

Это очень полезно, когда вы хотите что-то делать в стиле без очков . Примером является проверка наличия какой-либо строки в массиве, равной строке "foo". Есть обычный способ:

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

И есть бессмысленный путь:

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

Предпочтительный способ должен быть наиболее читабельным.

76 голосов
/ 01 августа 2009

Это эквивалентно

def tag_names
  @tag_names || tags.map { |tag| tag.name }.join(' ')
end
42 голосов
/ 20 ноября 2012

Отметим также, что магия амперсанд #to_proc может работать с любым классом, а не только с Symbol. Многие Rubyists предпочитают определять #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!"]

Ampersand & работает, отправляя сообщение to_proc на свой операнд, который в приведенном выше коде относится к классу Array. И так как я определил метод #to_proc для Array, строка становится такой:

[ 'Hello', 'Goodbye' ].map { |receiver| receiver.send( :+, ' world!' ) }
37 голосов
/ 01 августа 2009

Это сокращение для tags.map { |tag| tag.name }.join(' ')

32 голосов
/ 01 ноября 2016
tags.map(&:name)

То же, что и

tags.map{|tag| tag.name}

&:name просто использует символ в качестве имени метода для вызова.

14 голосов
/ 24 января 2014

Ответ Джоша Ли является почти правильным, за исключением того, что эквивалентный код 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);

  1. :first является объектом Symbol, поэтому, когда &:first передается методу карты в качестве параметра, вызывается Symbol # to_proc.

  2. map отправляет сообщение о вызове: first.to_proc с параметром [1,'a'], например, :first.to_proc.call([1,'a']) выполняется.

  3. Процедура to_proc в классе Symbol отправляет сообщение об отправке объекту массива ([1,'a']) с параметром (: first), например, [1,'a'].send(:first) выполняется.

  4. перебирает остальные элементы в [[1,'a'],[2,'b'],[3,'c']] объекте.

Это то же самое, что и выражение [[1,'a'],[2,'b'],[3,'c']].map(|e| e.first).

11 голосов
/ 09 апреля 2016

Здесь происходят две вещи, и важно понимать обе.

Как описано в других ответах, вызывается метод 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 без амперсанда:

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 ожидаемый блок.

5 голосов
/ 25 августа 2016

(&: name) - это сокращение от (&: name.to_proc), оно равно tags.map{ |t| t.name }.join(' ')

to_proc фактически реализовано в C

2 голосов
/ 01 сентября 2018

map (&: name) принимает перечисляемый объект (в вашем случае теги) и запускает метод имени для каждого элемента / тега, выводя каждое возвращаемое значение из метода.

Это сокращение для

array.map { |element| element.name }

, который возвращает массив имен элементов (тегов)

...