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