Znalazłem ten kod w RailsCast :
def tag_names
@tag_names || tags.map(&:name).join(' ')
end
Co robi (&:name)
w map(&:name)
średniej?
Znalazłem ten kod w RailsCast :
def tag_names
@tag_names || tags.map(&:name).join(' ')
end
Co robi (&:name)
w map(&:name)
średniej?
Odpowiedzi:
To jest skrót od tags.map(&:name.to_proc).join(' ')
Jeśli foo
jest to obiekt z to_proc
metodą, możesz przekazać ją do metody as &foo
, która wywoła ją foo.to_proc
i użyje jako bloku metody.
Symbol#to_proc
Metoda został dodany przez ActiveSupport ale został zintegrowany Ruby 1.8.7. To jest jego wdrożenie:
class Symbol
def to_proc
Proc.new do |obj, *args|
obj.send self, *args
end
end
end
&
, tj.tags.map(&:name.to_proc).join(' ')
Kolejny fajny skrót, nieznany wielu, to
array.each(&method(:foo))
co jest skrótem od
array.each { |element| foo(element) }
Dzwoniąc method(:foo)
, wzięliśmy Method
obiekt, self
który reprezentuje jego foo
metodę, i użyliśmy &
do oznaczenia, że ma to_proc
metodę, która konwertuje go na Proc
.
Jest to bardzo przydatne, gdy chcesz robić rzeczy bez punktów . Przykładem jest sprawdzenie, czy w tablicy jest jakikolwiek ciąg znaków, który jest taki sam "foo"
. Istnieje konwencjonalny sposób:
["bar", "baz", "foo"].any? { |str| str == "foo" }
I jest sposób bez punktów:
["bar", "baz", "foo"].any?(&"foo".method(:==))
Preferowany sposób powinien być najbardziej czytelny.
array.each{|e| foo(e)}
jest jeszcze krótszy :-) +1 tak czy inaczej
&method
?
[1,2,3].map(&Array.method(:new))
Jest to równoważne z
def tag_names
@tag_names || tags.map { |tag| tag.name }.join(' ')
end
Zwróćmy też uwagę, że znaki handlowe i #to_proc
magiczne mogą współpracować z dowolną klasą, nie tylko Symbolem. Wielu rubyistów decyduje się zdefiniować #to_proc
w klasie Array:
class Array
def to_proc
proc { |receiver| receiver.send *self }
end
end
# And then...
[ 'Hello', 'Goodbye' ].map &[ :+, ' world!' ]
#=> ["Hello world!", "Goodbye world!"]
Ampersand &
działa poprzez wysyłanie to_proc
wiadomości na swoim operandzie, który w powyższym kodzie jest klasy Array. A ponieważ zdefiniowałem #to_proc
metodę na tablicy, linia staje się:
[ 'Hello', 'Goodbye' ].map { |receiver| receiver.send( :+, ' world!' ) }
To jest skrót od tags.map { |tag| tag.name }.join(' ')
&
operator woła to_proc
swój operand. Więc nie jest to specyficzne dla metody map i faktycznie działa na każdej metodzie, która pobiera blok i przekazuje jeden lub więcej argumentów do bloku.
Odpowiedź Josha Lee jest prawie poprawna, tyle że równoważny kod Ruby powinien wyglądać następująco.
class Symbol
def to_proc
Proc.new do |receiver|
receiver.send self
end
end
end
nie
class Symbol
def to_proc
Proc.new do |obj, *args|
obj.send self, *args
end
end
end
Z tym kodem, gdy print [[1,'a'],[2,'b'],[3,'c']].map(&:first)
jest wykonywany, Ruby dzieli pierwsze wejście [1,'a']
na 1 i „a”, aby dać obj
1 i args*
„a”, aby spowodować błąd, ponieważ obiekt Fixnum 1 nie ma metody self (która jest: pierwsza).
Kiedy [[1,'a'],[2,'b'],[3,'c']].map(&:first)
jest wykonywany;
:first
jest obiektem Symbol, więc po &:first
podaniu metody mapy jako parametru wywoływany jest Symbol # to_proc.
map wysyła komunikat wywołania do: first.to_proc z parametrem [1,'a']
, np. :first.to_proc.call([1,'a'])
jest wykonywany.
Procedura to_proc w klasie Symbol wysyła komunikat wysyłania do obiektu tablicy ( [1,'a']
) z parametrem (: first), np. [1,'a'].send(:first)
jest wykonywany.
wykonuje iterację nad resztą elementów w [[1,'a'],[2,'b'],[3,'c']]
obiekcie.
Jest to to samo, co wykonywanie [[1,'a'],[2,'b'],[3,'c']].map(|e| e.first)
wyrażenia.
[1,2,3,4,5,6].inject(&:+)
- zastrzyk oczekuje lambdy z dwoma parametrami (notka i przedmiot) i :+.to_proc
dostarcza ją - Proc.new |obj, *args| { obj.send(self, *args) }
lub{ |m, o| m.+(o) }
Działają się tutaj dwie rzeczy i ważne jest, aby zrozumieć oba.
Jak opisano w innych odpowiedziach, Symbol#to_proc
metoda jest wywoływana.
Ale powodem to_proc
wywołania tego symbolu jest to, że jest przekazywany map
jako argument blokowy. Umieszczenie &
przed argumentem w wywołaniu metody powoduje, że jest on przekazywany w ten sposób. Dotyczy to każdej metody Ruby, nie tylkomap
symboli.
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
Zostaje zamieniana na Proc
ponieważ jest przekazywana w postaci bloku. Możemy to pokazać, próbując przekazać procesor .map
bez znaku handlowego:
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"]
Mimo że nie trzeba go konwertować, metoda nie będzie wiedziała, jak go użyć, ponieważ oczekuje argumentu blokowego. Podanie go &
daje oczekiwany .map
blok.
Zasadniczo wykonuje wywołanie metody tag.name
dla każdego znacznika w tablicy.
Jest to uproszczony rubinowy skrót.
Chociaż mamy już świetne odpowiedzi, patrząc z perspektywy początkującego chciałbym dodać dodatkowe informacje:
Co oznacza mapa (i: nazwa) w Ruby?
Oznacza to, że przekazujesz inną metodę jako parametr do funkcji mapy. (W rzeczywistości podajesz symbol, który zamienia się w proc. Ale to nie jest tak ważne w tym konkretnym przypadku).
Ważne jest to, że masz method
nazwę, name
która będzie używana przez metodę map jako argument zamiast tradycyjnego block
stylu.
Po pierwsze, &:name
jest skrótem do &:name.to_proc
, w którym :name.to_proc
zwraca Proc
(coś, co jest podobne, ale nie identyczne jak lambda), które po wywołaniu z obiektem jako (pierwszy) argument wywołuje name
metodę na tym obiekcie.
Po drugie, podczas gdy &
w def foo(&block) ... end
konwertuje blok przekazany na foo
a Proc
, robi się odwrotnie, gdy jest stosowany do a Proc
.
Zatem &:name.to_proc
jest blok, który przyjmuje obiekt jako argument i wywołuje na nim name
metodę, tj { |o| o.name }
.
To jest tak samo jak poniżej:
def tag_names
if @tag_names
@tag_names
else
tags.map{ |t| t.name }.join(' ')
end