Что означает map(&:name) в Ruby?
-
07-07-2019 - |
Вопрос
Я нашел этот код в RailsCast:
def tag_names
@tag_names || tags.map(&:name).join(' ')
end
Что это (&:name)
в map(&:name)
иметь в виду?
Решение
Это сокращение от tags.map (& amp;: name.to_proc) .join ('')
Если foo
является объектом с методом to_proc
, вы можете передать его в метод как & amp; 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) }
При вызове метода (: foo)
мы взяли объект Method
из self
, который представляет его метод foo
и использовал & amp;
, чтобы указать, что у него есть to_proc
метод , который преобразует его в Proc
.
Это очень полезно, когда вы хотите использовать стиль без точек . Пример - проверить, есть ли какая-либо строка в массиве, равная строке " foo "
. Есть общепринятый способ:
["bar", "baz", "foo"].any? { |str| str == "foo" }
И есть бессмысленный способ:
["bar", "baz", "foo"].any?(&"foo".method(:==))
Предпочтительный способ должен быть наиболее читаемым.
Это эквивалентно
def tag_names
@tag_names || tags.map { |tag| tag.name }.join(' ')
end
Отметим также, что магия амперсанда #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 & amp;
работает, отправляя сообщение to_proc
о своем операнде, который в приведенном выше коде относится к классу Array. И так как я определил метод #to_proc
в массиве, строка становится такой:
[ 'Hello', 'Goodbye' ].map { |receiver| receiver.send( :+, ' world!' ) }
Это сокращение от tags.map {| tag | tag.name} .join ('')
tags.map(&:name)
- это то же самое, что и
tags.map{|tag| tag.name}
& amp;: name
просто использует символ в качестве имени метода для вызова.
Ответ Джоша Ли почти правильный, за исключением того, что эквивалентный код 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 и «а», чтобы дать obj
1 и args*
'a', чтобы вызвать ошибку, поскольку объект Fixnum 1 не имеет метода self (то есть :first).
Когда [[1,'a'],[2,'b'],[3,'c']].map(&:first)
выполняется;
:first
является объектом символа, поэтому, когда&:first
передается методу карты в качестве параметра, вызывается символ #to_proc.карта отправляет сообщение о вызове в :first.to_proc с параметром
[1,'a']
, например,:first.to_proc.call([1,'a'])
выполняется.Процедура 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)
выражение.
Здесь происходят две вещи, и важно понимать обе.
Как описано в других ответах, вызывается метод Symbol # to_proc
.
Но причина того, что to_proc
вызывается для символа, заключается в том, что он передается в map
в качестве аргумента блока. Помещение & amp;
перед аргументом в вызове метода приводит к его передаче таким способом. Это верно для любого метода 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"]
Несмотря на то, что его не нужно конвертировать, метод не будет знать, как его использовать, потому что он ожидает аргумент блока. Передав его с помощью & amp;
, вы получите .map
ожидаемый блок.
(& amp;: name) - это сокращение от (& amp;: name.to_proc), оно совпадает с tags.map {| t | t.name} .join ('')
to_proc фактически реализован на C
Хотя у нас уже есть отличные ответы, глядя на новичка, я хотел бы добавить дополнительную информацию:
Что означает карта (& amp;: имя) в Ruby?
Это означает, что вы передаете другой метод в качестве параметра функции map. (На самом деле вы передаете символ, который превращается в процесс. Но это не так важно в данном конкретном случае).
Важно то, что у вас есть метод
с именем name
, который будет использоваться методом карты в качестве аргумента вместо традиционного block
стиль.
map (& amp;: name) берет перечисляемый объект (тэги в вашем случае) и запускает метод name для каждого элемента / тэга, выводя каждое возвращаемое значение из метода.
Это сокращение для
array.map { |element| element.name }
, который возвращает массив имен элементов (тегов)
Здесь : name
- это символ, который указывает на метод name
объекта тега.
Когда мы передаем & amp:: name
в map
, он будет обрабатывать name
как объект proc.
Для краткости tags.map (& amp;: name)
действует как:
tags.map do |tag|
tag.name
end
это значит
array.each(&:to_sym.to_proc)
Он в основном выполняет вызов метода tag.name
для каждого тега в массиве.
Это упрощенная рубиновая стенография.
Это так же, как показано ниже:
def tag_names
if @tag_names
@tag_names
else
tags.map{ |t| t.name }.join(' ')
end