Was bedeutet map (&: name) in Ruby?


496

Ich habe diesen Code in einem RailsCast gefunden :

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

Was bedeutet das (&:name)in map(&:name)?


122
Ich habe übrigens gehört, dass dies "Brezel-Doppelpunkt" genannt wird.
Josh Lee

6
Haha. Ich weiß das als kaufmännisches Und. Ich habe noch nie gehört, dass es eine "Brezel" heißt, aber das macht Sinn.
DragonFax

74
Es "irreführend" zu nennen, ist irreführend, wenn auch eingängig. Es gibt kein "&:" in Rubin. Das kaufmännische Und (&) ist ein "unärer kaufmännisches Und-Operator" mit einem zusammengeschobenen Symbol. Wenn überhaupt, ist es ein "Brezelsymbol". Ich sage nur.
Fontno

3
tags.map (&: name) ist eine Sortierung von tags.map {| s | s.name}
kaushal sharma

3
"Brezel Doppelpunkt" klingt wie eine schmerzhafte Krankheit ... aber ich mag den Namen für dieses Symbol :)
Zmorris

Antworten:


517

Es ist eine Abkürzung für tags.map(&:name.to_proc).join(' ')

Wenn fooes sich um ein Objekt mit einer to_procMethode handelt, können Sie es an eine Methode als übergeben &foo, die diese aufruft foo.to_procund als Block der Methode verwendet.

Die Symbol#to_procMethode wurde ursprünglich von ActiveSupport hinzugefügt, wurde jedoch in Ruby 1.8.7 integriert. Dies ist seine Umsetzung:

class Symbol
  def to_proc
    Proc.new do |obj, *args|
      obj.send self, *args
    end
  end
end

41
Dies ist eine bessere Antwort als meine.
Oliver N.

91
tags.map (: name.to_proc) ist selbst eine Abkürzung für tags.map {| tag | tag.name}
Simone Carletti

5
Dies ist kein gültiger Ruby-Code. Sie benötigen weiterhin den &, dhtags.map(&:name.to_proc).join(' ')
horseyguy

5
Das Symbol # to_proc ist in C implementiert, nicht in Ruby, aber so würde es in Ruby aussehen.
Andrew Grimm

5
@ AndrewGrimm wurde zuerst in Ruby on Rails mit diesem Code hinzugefügt. Es wurde dann als native Ruby-Funktion in Version 1.8.7 hinzugefügt.
Cameron Martin

175

Eine andere coole Abkürzung, die vielen unbekannt ist, ist

array.each(&method(:foo))

Das ist eine Abkürzung für

array.each { |element| foo(element) }

Durch Aufrufen haben method(:foo)wir ein MethodObjekt genommen self, das seine fooMethode darstellt, und das verwendet, &um anzuzeigen, dass es eine to_proc Methode hat , die es in eine konvertiert Proc.

Dies ist sehr nützlich, wenn Sie einen punktfreien Stil ausführen möchten . Ein Beispiel ist die Überprüfung, ob ein Array eine Zeichenfolge enthält, die der Zeichenfolge entspricht "foo". Es gibt den herkömmlichen Weg:

["bar", "baz", "foo"].any? { |str| str == "foo" }

Und da ist der punktfreie Weg:

["bar", "baz", "foo"].any?(&"foo".method(:==))

Der bevorzugte Weg sollte der am besten lesbare sein.


25
array.each{|e| foo(e)}ist noch kürzer :-) +1 sowieso
Jared Beck

Könnten Sie einen Konstruktor einer anderen Klasse mit zuordnen &method?
Holographisches Prinzip

3
@finishingmove ja ich denke. Versuchen Sie dies[1,2,3].map(&Array.method(:new))
Gerry

78

Es ist gleichbedeutend mit

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

45

Beachten wir auch, dass kaufmännisches Und-Zeichen- #to_procMagie mit jeder Klasse funktionieren kann, nicht nur mit Symbol. Viele Rubyisten definieren die #to_procArray-Klasse wie folgt :

class Array
  def to_proc
    proc { |receiver| receiver.send *self }
  end
end

# And then...

[ 'Hello', 'Goodbye' ].map &[ :+, ' world!' ]
#=> ["Hello world!", "Goodbye world!"]

Das kaufmännische Und &sendet eine to_procNachricht an seinen Operanden, der im obigen Code zur Array-Klasse gehört. Und da ich die #to_procMethode für das Array definiert habe , wird die Zeile wie folgt:

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

Das ist reines Gold!
Kubak

38

Es ist eine Abkürzung für tags.map { |tag| tag.name }.join(' ')


Nein, es ist in Ruby 1.8.7 und höher.
Chuck

Ist es eine einfache Redewendung für Map oder interpretiert Ruby das '&' immer auf eine bestimmte Weise?
Collimarco

7
@collimarco: Wie Jleedev in seiner Antwort sagt, &ruft der unäre Operator to_procseinen Operanden auf. Es ist also nicht spezifisch für die Map-Methode und funktioniert tatsächlich mit jeder Methode, die einen Block nimmt und ein oder mehrere Argumente an den Block übergibt.
Chuck

36
tags.map(&:name)

ist das gleiche wie

tags.map{|tag| tag.name}

&:name verwendet nur das Symbol als den aufzurufenden Methodennamen.


1
Die Antwort, nach der ich gesucht habe, und nicht speziell nach Prozessen (aber das war die Frage des
Antragstellers

Gute Antwort! für mich gut geklärt.
Apadana

14

Josh Lees Antwort ist fast richtig, außer dass der entsprechende Ruby-Code wie folgt lauten sollte.

class Symbol
  def to_proc
    Proc.new do |receiver|
      receiver.send self
    end
  end
end

nicht

class Symbol
  def to_proc
    Proc.new do |obj, *args|
      obj.send self, *args
    end
  end
end

Wenn dieser Code print [[1,'a'],[2,'b'],[3,'c']].map(&:first)ausgeführt wird, teilt Ruby die erste Eingabe [1,'a']in 1 und 'a' auf, um obj1 zu geben, und args*'a', um einen Fehler zu verursachen, da das Fixnum-Objekt 1 nicht über die Methode self verfügt (dh: first).


Wann [[1,'a'],[2,'b'],[3,'c']].map(&:first)wird ausgeführt;

  1. :firstist ein Symbol-Objekt. Wenn &:firstalso eine Map-Methode als Parameter angegeben wird, wird Symbol # to_proc aufgerufen.

  2. map sendet eine Aufrufnachricht an: first.to_proc mit Parameter [1,'a'], zB :first.to_proc.call([1,'a'])wird ausgeführt.

  3. Die Prozedur to_proc in der Symbolklasse sendet eine Sende-Nachricht an ein Array-Objekt ( [1,'a']) mit dem Parameter (: first), z [1,'a'].send(:first). B. wird ausgeführt.

  4. iteriert über den Rest der Elemente im [[1,'a'],[2,'b'],[3,'c']]Objekt.

Dies ist dasselbe wie das Ausführen eines [[1,'a'],[2,'b'],[3,'c']].map(|e| e.first)Ausdrucks.


1
Josh Lees Antwort ist absolut richtig, wie Sie sehen können, wenn Sie darüber nachdenken [1,2,3,4,5,6].inject(&:+), dass Inject ein Lambda mit zwei Parametern (Memo und Item) erwartet und es :+.to_procliefert - Proc.new |obj, *args| { obj.send(self, *args) }oder{ |m, o| m.+(o) }
Uri Agassi

11

Hier passieren zwei Dinge, und es ist wichtig, beide zu verstehen.

Wie in anderen Antworten beschrieben, wird die Symbol#to_procMethode aufgerufen.

Der Grund to_proc, warum das Symbol aufgerufen wird, ist, dass es mapals Blockargument übergeben wird. Wenn Sie &in einem Methodenaufruf vor einem Argument stehen, wird es auf diese Weise übergeben. Dies gilt für jede Ruby-Methode, nicht nur mapfür Symbole.

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)

Das Symbolwird in a konvertiert, Procweil es als Block übergeben wird. Wir können dies zeigen, indem wir versuchen, einen Proc .mapohne kaufmännisches Und weiterzugeben :

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"]

Obwohl es nicht konvertiert werden muss, weiß die Methode nicht, wie es verwendet werden soll, da sie ein Blockargument erwartet. Passen Sie es mit &gibt .mapden Block , es erwartet.


Dies ist ehrlich gesagt die beste Antwort. Sie erklären den Mechanismus hinter dem kaufmännischen Und und warum wir einen Proc erhalten, den ich erst nach Ihrer Antwort erhalten habe. Vielen Dank.
Fralcon

5

(&: name) ist die Abkürzung für (&: name.to_proc) und entspricht tags.map{ |t| t.name }.join(' ')

to_proc ist tatsächlich in C implementiert


5

map (&: name) verwendet ein aufzählbares Objekt (in Ihrem Fall Tags) und führt die Namensmethode für jedes Element / Tag aus, wobei jeder zurückgegebene Wert von der Methode ausgegeben wird.

Es ist eine Abkürzung für

array.map { |element| element.name }

Dies gibt das Array der Elementnamen (Tag-Namen) zurück


3

Grundsätzlich wird der Methodenaufruf tag.namefür jedes Tag im Array ausgeführt.

Es ist eine vereinfachte Rubin-Abkürzung.


2

Obwohl wir bereits gute Antworten haben, möchte ich aus der Perspektive eines Anfängers die zusätzlichen Informationen hinzufügen:

Was bedeutet map (&: name) in Ruby?

Dies bedeutet, dass Sie eine andere Methode als Parameter an die Kartenfunktion übergeben. (In Wirklichkeit übergeben Sie ein Symbol, das in einen Proc umgewandelt wird. Dies ist jedoch in diesem speziellen Fall nicht so wichtig.)

Wichtig ist, dass Sie einen methodNamen haben name, der von der Map-Methode als Argument anstelle des traditionellen blockStils verwendet wird.


2

Zuerst, &:name ist dies eine Verknüpfung für &:name.to_proc, bei :name.to_procder ein Proc(etwas, das einem Lambda ähnlich, aber nicht identisch ist) zurückgegeben wird, das beim Aufrufen mit einem Objekt als (erstem) Argument die nameMethode für dieses Objekt aufruft .

Zweitens während & in def foo(&block) ... endwandelt ein Block zu übergeben , fooum einen Proc, sie tut das Gegenteil , wenn sie einen aufgebracht Proc.

Es handelt sich also &:name.to_procum einen Block, der ein Objekt als Argument verwendet und die nameMethode dafür aufruft , d { |o| o.name }. H.


1

Hier :nameist das Symbol, das auf die Methode namedes Tag-Objekts verweist. Wenn wir zu gehen &:name, mapwird es nameals Proc-Objekt behandelt. Kurz gesagt, tags.map(&:name)fungiert als:

tags.map do |tag|
  tag.name
end


0

Es ist das gleiche wie unten:

def tag_names
  if @tag_names
    @tag_names
  else
    tags.map{ |t| t.name }.join(' ')
end
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.