Co oznacza mapa (i: nazwa) w Ruby?


496

Znalazłem ten kod w RailsCast :

def tag_names
  @tag_names || tags.map(&:name).join(' ')
end

Co robi (&:name)w map(&:name)średniej?


122
Nawiasem mówiąc, słyszałem, że to się nazywa „okrężnica precla”.
Josh Lee,

6
Ha ha. Wiem to jako Ampersand. Nigdy nie słyszałem, by nazywało się to „preclem”, ale to ma sens.
DragonFax,

74
Nazywanie tego „okrężnicą precla” jest mylące, choć chwytliwe. W rubinie nie ma „&:”. Znak ampersand (&) to „jednoargumentowy operator znaku ampersand” z połączonym symbolem:. Jeśli już, to „symbol precla”. Tylko mówię.
fontno

3
tags.map (&: name) to rodzaj tags.map {| s | s.name}
kaushal sharma 17.07.16

3
„precel jelita grubego” brzmi jak bolesny stan zdrowia ... ale podoba mi się nazwa tego symbolu :)
zmorris

Odpowiedzi:


517

To jest skrót od tags.map(&:name.to_proc).join(' ')

Jeśli foojest to obiekt z to_procmetodą, możesz przekazać ją do metody as &foo, która wywoła ją foo.to_proci użyje jako bloku metody.

Symbol#to_procMetoda 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

41
To lepsza odpowiedź niż moja.
Oliver N.

91
tags.map (: name.to_proc) sam w sobie jest skrótem dla tags.map {| tag | tag.name}
Simone Carletti

5
to nie jest poprawny kod ruby, nadal potrzebujesz &, tj.tags.map(&:name.to_proc).join(' ')
horseyguy

5
Symbol # to_proc jest zaimplementowany w C, a nie w Ruby, ale tak by to wyglądało w Ruby.
Andrew Grimm,

5
@AndrewGrimm został po raz pierwszy dodany w Ruby on Rails przy użyciu tego kodu. Następnie został dodany jako natywna funkcja ruby ​​w wersji 1.8.7.
Cameron Martin

174

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 Methodobiekt, selfktóry reprezentuje jego foometodę, 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.


25
array.each{|e| foo(e)}jest jeszcze krótszy :-) +1 tak czy inaczej
Jared Beck

Czy możesz zmapować konstruktor innej klasy za pomocą &method?
zasada holograficzna

3
@finishingmove tak, tak myślę. Spróbuj tego[1,2,3].map(&Array.method(:new))
Gerry

78

Jest to równoważne z

def tag_names
  @tag_names || tags.map { |tag| tag.name }.join(' ')
end

45

Zwróćmy też uwagę, że znaki handlowe i #to_procmagiczne mogą współpracować z dowolną klasą, nie tylko Symbolem. Wielu rubyistów decyduje się zdefiniować #to_procw 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_procwiadomości na swoim operandzie, który w powyższym kodzie jest klasy Array. A ponieważ zdefiniowałem #to_procmetodę na tablicy, linia staje się:

[ 'Hello', 'Goodbye' ].map { |receiver| receiver.send( :+, ' world!' ) }

To jest czyste złoto!
kubak

38

To jest skrót od tags.map { |tag| tag.name }.join(' ')


Nie, jest w Ruby 1.8.7 i nowszych.
Chuck

Czy jest to prosty idiom dla mapy lub Ruby zawsze interpretuje „&” w określony sposób?
collimarco

7
@collimarco: Jak mówi Jleedev w swojej odpowiedzi, jednoargumentowy &operator woła to_procswó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.
Chuck

36
tags.map(&:name)

jest taki sam jak

tags.map{|tag| tag.name}

&:name po prostu używa symbolu jako nazwy metody, która ma zostać wywołana.


1
Odpowiedź szukałem, zamiast specjalnie dla proca (ale to była kwestia Requestery)
matrim_c

Niezła odpowiedź! dobrze dla mnie wyjaśnione.
apadana

14

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ć obj1 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;

  1. :firstjest obiektem Symbol, więc po &:firstpodaniu metody mapy jako parametru wywoływany jest Symbol # to_proc.

  2. map wysyła komunikat wywołania do: first.to_proc z parametrem [1,'a'], np. :first.to_proc.call([1,'a'])jest wykonywany.

  3. 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.

  4. 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
Odpowiedź Josha Lee jest absolutnie poprawna, jak widać po przemyśleniu [1,2,3,4,5,6].inject(&:+)- zastrzyk oczekuje lambdy z dwoma parametrami (notka i przedmiot) i :+.to_procdostarcza ją - Proc.new |obj, *args| { obj.send(self, *args) }lub{ |m, o| m.+(o) }
Uri Agassi

11

Działają się tutaj dwie rzeczy i ważne jest, aby zrozumieć oba.

Jak opisano w innych odpowiedziach, Symbol#to_procmetoda jest wywoływana.

Ale powodem to_procwywołania tego symbolu jest to, że jest przekazywany mapjako 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)

SymbolZostaje zamieniana na Procponieważ jest przekazywana w postaci bloku. Możemy to pokazać, próbując przekazać procesor .mapbez 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 .mapblok.


To jest naprawdę najlepsza udzielona odpowiedź. Wyjaśniasz mechanizm ampersand i dlaczego kończymy procesem, którego nie dostałem do twojej odpowiedzi. Dziękuję Ci.
Fralcon,

5

(&: name) to skrót od (&: name.to_proc), to samo co tags.map{ |t| t.name }.join(' ')

to_proc jest faktycznie zaimplementowane w C.


5

mapa (i: nazwa) pobiera wymienny obiekt (tagi w twoim przypadku) i uruchamia metodę name dla każdego elementu / tagu, wypisując każdą zwróconą wartość z metody.

To jest skrót od

array.map { |element| element.name }

która zwraca tablicę nazw elementów (tagów)


3

Zasadniczo wykonuje wywołanie metody tag.namedla każdego znacznika w tablicy.

Jest to uproszczony rubinowy skrót.


2

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 methodnazwę, namektóra będzie używana przez metodę map jako argument zamiast tradycyjnego blockstylu.


2

Po pierwsze, &:namejest skrótem do &:name.to_proc, w którym :name.to_proczwraca Proc(coś, co jest podobne, ale nie identyczne jak lambda), które po wywołaniu z obiektem jako (pierwszy) argument wywołuje namemetodę na tym obiekcie.

Po drugie, podczas gdy &w def foo(&block) ... endkonwertuje blok przekazany na fooa Proc, robi się odwrotnie, gdy jest stosowany do a Proc.

Zatem &:name.to_procjest blok, który przyjmuje obiekt jako argument i wywołuje na nim namemetodę, tj { |o| o.name }.


1

Oto :namesymbol wskazujący na metodę nameznacznika obiektu. Kiedy przechodzimy &:namedo map, będzie traktowany namejako obiekt proc. W skrócie tags.map(&:name)działa jako:

tags.map do |tag|
  tag.name
end


0

To jest tak samo jak poniżej:

def tag_names
  if @tag_names
    @tag_names
  else
    tags.map{ |t| t.name }.join(' ')
end
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.