Я нашел этот код в RailsCast :
def tag_names
@tag_names || tags.map(&:name).join(' ')
end
Что значит (&:name)
в map(&:name)
?
Я нашел этот код в RailsCast :
def tag_names
@tag_names || tags.map(&:name).join(' ')
end
Что значит (&:name)
в map(&:name)
?
Ответы:
Это сокращение для tags.map(&:name.to_proc).join(' ')
Если foo
это объект с to_proc
методом, то вы можете передать его методу as &foo
, который будет вызывать foo.to_proc
и использовать его как блок метода.
Symbol#to_proc
Метод был первоначально добавлен ActiveSupport , но был интегрирован в Руби 1.8.7. Это его реализация:
class Symbol
def to_proc
Proc.new do |obj, *args|
obj.send self, *args
end
end
end
&
, т.е.tags.map(&:name.to_proc).join(' ')
Еще одна классная стенография, неизвестная многим,
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(:==))
Предпочтительный способ должен быть наиболее читабельным.
array.each{|e| foo(e)}
короче еще :-) +1 в любом случае
&method
?
[1,2,3].map(&Array.method(:new))
Это эквивалентно
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 &
работает, отправляя to_proc
сообщение на свой операнд, который в приведенном выше коде относится к классу Array. И так как я определил #to_proc
метод на массиве, строка становится:
[ 'Hello', 'Goodbye' ].map { |receiver| receiver.send( :+, ' world!' ) }
Это сокращение для tags.map { |tag| tag.name }.join(' ')
&
оператор вызывает to_proc
свой операнд. Так что он не является специфичным для метода map и фактически работает с любым методом, который принимает блок и передает один или несколько аргументов в блок.
Ответ Джоша Ли является почти правильным, за исключением того, что эквивалентный код 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)
выполняется;
:first
является объектом Symbol, поэтому когда &:first
он передается методу карты в качестве параметра, вызывается Symbol # to_proc.
Карта отправляет сообщение о вызове: first.to_proc с параметром [1,'a']
, например, :first.to_proc.call([1,'a'])
выполняется.
Процедура to_proc в классе Symbol отправляет сообщение отправки в объект массива ( [1,'a']
) с параметром (: first), например, [1,'a'].send(:first)
выполняется.
перебирает остальные элементы [[1,'a'],[2,'b'],[3,'c']]
объекта.
Это то же самое, что выполнение [[1,'a'],[2,'b'],[3,'c']].map(|e| e.first)
выражения.
[1,2,3,4,5,6].inject(&:+)
инъекция ожидает лямбду с двумя параметрами (записку и предмет) и :+.to_proc
доставляет ее - Proc.new |obj, *args| { obj.send(self, *args) }
или{ |m, o| m.+(o) }
Здесь происходят две вещи, и важно понимать обе.
Как описано в других ответах, 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
ожидаемый блок.
Он в основном выполняет вызов метода tag.name
для каждого тега в массиве.
Это упрощенная рубиновая стенография.
Хотя у нас уже есть отличные ответы, глядя на перспективу новичка, я хотел бы добавить дополнительную информацию:
Что означает карта (&: name) в Ruby?
Это означает, что вы передаете другой метод в качестве параметра функции map. (На самом деле вы передаете символ, который превращается в процесс. Но это не так важно в данном конкретном случае).
Важно то, что у вас есть method
имя, name
которое будет использоваться методом карты в качестве аргумента вместо традиционного block
стиля.
Во-первых, &:name
это ярлык для &:name.to_proc
, где :name.to_proc
возвращает Proc
(что-то похожее, но не идентичное лямбда-выражению), которое при вызове с объектом в качестве (первого) аргумента вызывает name
метод для этого объекта.
Во-вторых, в то время как &
in def foo(&block) ... end
преобразует блок, переданный в foo
a Proc
, он делает обратное при применении к a Proc
.
Таким образом, &:name.to_proc
это блок , который принимает объект в качестве аргумента и вызывает name
метод на него, то есть { |o| o.name }
.
Это так же, как показано ниже:
def tag_names
if @tag_names
@tag_names
else
tags.map{ |t| t.name }.join(' ')
end