Я знайшов цей код на 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
методом, то ви можете передати його методу як &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
&
, тобтоtags.map(&:name.to_proc).join(' ')
Ще одна класна стенограма, невідома багатьом, є
array.each(&method(:foo))
що є скороченням
array.each { |element| foo(element) }
Зателефонувавши, method(:foo)
ми взяли Method
об'єкт, self
який представляє його foo
метод, і використовували &
для позначення того, що у нього є to_proc
метод, який перетворює його в a 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
метод у Array, рядок стає:
[ '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', щоб викликати помилку, оскільки об'єкт 1 Fixnum не має методу self (який є: першим).
Коли [[1,'a'],[2,'b'],[3,'c']].map(&:first)
виконується;
:first
є об'єктом Symbol, тому, коли &:first
метод вказаний як параметр як параметр, викликається символ # to_proc.
карта надсилає повідомлення про виклик: first.to_proc з параметром [1,'a']
, наприклад, :first.to_proc.call([1,'a'])
виконується.
Процедура to_proc класу Symbol надсилає повідомлення відправника об’єкту масиву ([1,'a']
) з параметром (: перший), наприклад, [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
яке буде використано методом map як аргумент замість традиційного block
стилю.
По-перше, &:name
це ярлик для &:name.to_proc
, де :name.to_proc
повертається a Proc
(щось схоже, але не тотожне лямбда), яке при виклику з об'єктом як (перший) аргумент викликає name
метод на цьому об'єкті.
По- друге, в той час як &
в def foo(&block) ... end
звернених блок - передається foo
до Proc
, він робить протилежне при застосуванні доProc
.
Таким чином, &:name.to_proc
це блок, який приймає об’єкт як аргумент і викликає на ньому name
метод, тобто { |o| o.name }
.
Це те саме, що нижче:
def tag_names
if @tag_names
@tag_names
else
tags.map{ |t| t.name }.join(' ')
end