Cosa significa map (&: name) in Ruby?


496

Ho trovato questo codice in un RailsCast :

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

Cosa significa " (&:name)in" map(&:name)?


122
A proposito, ho sentito questo chiamato "pretzel colon".
Josh Lee,

6
Haha. Lo so come una e commerciale. Non l'ho mai sentito chiamato "pretzel" ma ha senso.
DragonFax,

74
Chiamarlo "pretzel colon" è fuorviante, sebbene accattivante. Non c'è "&:" in ruby. La e commerciale (&) è un "operatore e commerciale unario" con un simbolo unito: simbolo. Semmai, è un "simbolo pretzel". Sto solo dicendo.
fontno,

3
tags.map (&: name) è l'ordinamento di tags.map {| s | s.name}
kaushal sharma,

3
"pretzel colon" sembra una condizione medica dolorosa ... ma mi piace il nome di questo simbolo :)
zmorris

Risposte:


517

È una scorciatoia per tags.map(&:name.to_proc).join(' ')

Se fooè un oggetto con un to_procmetodo, puoi passarlo a un metodo as &foo, che lo chiamerà foo.to_proce lo utilizzerà come blocco del metodo.

Il Symbol#to_procmetodo è stato originariamente aggiunto da ActiveSupport ma è stato integrato in Ruby 1.8.7. Questa è la sua implementazione:

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

42
Questa è una risposta migliore della mia.
Oliver N.

91
tags.map (: name.to_proc) è di per sé una scorciatoia per tags.map {| tag | tag.name}
Simone Carletti,

5
questo non è un codice rubino valido, hai ancora bisogno di &, cioètags.map(&:name.to_proc).join(' ')
horseyguy,

5
Il simbolo # to_proc è implementato in C, non in Ruby, ma è come apparirebbe in Ruby.
Andrew Grimm,

5
@AndrewGrimm è stato aggiunto per la prima volta in Ruby on Rails, usando quel codice. È stato quindi aggiunto come funzionalità ruby ​​nativa nella versione 1.8.7.
Cameron Martin,

175

Un'altra scorciatoia interessante, sconosciuta a molti, è

array.each(&method(:foo))

che è una scorciatoia per

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

Chiamando method(:foo)abbiamo preso un Methodoggetto selfche rappresenta il suo foometodo e abbiamo usato il &per indicare che ha un to_proc metodo che lo converte in a Proc.

Questo è molto utile quando vuoi fare cose senza stile. Un esempio è verificare se esiste una stringa in un array uguale alla stringa "foo". C'è il modo convenzionale:

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

E c'è il modo senza punti:

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

Il modo preferito dovrebbe essere quello più leggibile.


25
array.each{|e| foo(e)}è ancora più corto :-) +1 comunque
Jared Beck

Potresti mappare un costruttore di un'altra classe usando &method?
principio olografico

3
@finishingmove sì, credo. Prova questo[1,2,3].map(&Array.method(:new))
Gerry,

78

È equivalente a

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

45

Mentre notiamo anche che la #to_procmagia e commerciale può funzionare con qualsiasi classe, non solo con Symbol. Molti rubyisti scelgono di definire #to_procsulla classe Array:

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

# And then...

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

La e commerciale &funziona inviando un to_procmessaggio sul suo operando, che, nel codice sopra, è di classe Array. E poiché ho definito il #to_procmetodo su Array, la linea diventa:

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

Questo è oro puro!
Kubak,

38

È una scorciatoia per tags.map { |tag| tag.name }.join(' ')


No, è in Ruby 1.8.7 e versioni successive.
Chuck

È un semplice idioma per map o Ruby interpreta sempre '&' in un modo particolare?
Collimarco,

7
@collimarco: come dice jleedev nella sua risposta, l' &operatore unario chiama to_procil suo operando. Quindi non è specifico per il metodo map, e in effetti funziona su qualsiasi metodo che prende un blocco e passa uno o più argomenti al blocco.
Chuck

36
tags.map(&:name)

equivale a

tags.map{|tag| tag.name}

&:name usa solo il simbolo come nome del metodo da chiamare.


1
La risposta che stavo cercando, piuttosto che specificamente per procs (ma questa era la domanda dei richiedenti)
matrim_c

Bella risposta! chiarito per me bene.
apadana,

14

La risposta di Josh Lee è quasi corretta, tranne per il fatto che il codice Ruby equivalente avrebbe dovuto essere il seguente.

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

non

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

Con questo codice, quando print [[1,'a'],[2,'b'],[3,'c']].map(&:first)viene eseguito, Ruby divide il primo input [1,'a']in 1 e 'a' per dare obj1 e args*'a' per causare un errore poiché l'oggetto Fixnum 1 non ha il metodo self (che è: primo).


Quando [[1,'a'],[2,'b'],[3,'c']].map(&:first)viene eseguito;

  1. :firstè un oggetto Symbol, quindi quando &:firstviene assegnato a un metodo map come parametro, viene richiamato Symbol # to_proc.

  2. map invia il messaggio di chiamata a: first.to_proc con il parametro [1,'a'], ad es., :first.to_proc.call([1,'a'])viene eseguito.

  3. La procedura to_proc nella classe Symbol invia un messaggio di invio a un oggetto array ( [1,'a']) con il parametro (: primo), ad es. [1,'a'].send(:first)viene eseguito.

  4. scorre il resto degli elementi [[1,'a'],[2,'b'],[3,'c']]nell'oggetto.

È lo stesso dell'esecuzione [[1,'a'],[2,'b'],[3,'c']].map(|e| e.first)dell'espressione.


1
La risposta di Josh Lee è assolutamente corretta, come puoi vedere pensando [1,2,3,4,5,6].inject(&:+): iniettare si aspetta una lambda con due parametri (memo e oggetto) e la :+.to_procconsegna - Proc.new |obj, *args| { obj.send(self, *args) }oppure{ |m, o| m.+(o) }
Uri Agassi,

11

Qui stanno accadendo due cose ed è importante capirle entrambe.

Come descritto in altre risposte, Symbol#to_procviene chiamato il metodo.

Ma la ragione per cui to_procviene chiamato il simbolo è perché viene passato mapcome argomento a blocchi. Posizionare &davanti a un argomento in una chiamata di metodo fa sì che venga passato in questo modo. Questo vale per qualsiasi metodo Ruby, non solo mapcon i simboli.

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)

La Symbolviene convertito in un Procperché è passato come un blocco. Possiamo dimostrarlo provando a passare un proc a .mapsenza la e commerciale:

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

Anche se non ha bisogno di essere convertito, il metodo non saprà come usarlo perché si aspetta un argomento a blocchi. Passandolo con si &ottiene .mapil blocco che si aspetta.


Questa è onestamente la migliore risposta data. Spieghi il meccanismo dietro la e commerciale e perché finiamo con un proc, che non ho avuto fino alla tua risposta. Grazie.
Fralcon,

5

(&: name) è l'abbreviazione di (&: name.to_proc) è uguale a tags.map{ |t| t.name }.join(' ')

to_proc è effettivamente implementato in C


5

mappa (e: nome) accetta un oggetto enumerabile (tag nel tuo caso) ed esegue il metodo name per ogni elemento / tag, producendo ogni valore restituito dal metodo.

È una scorciatoia per

array.map { |element| element.name }

che restituisce la matrice dei nomi degli elementi (tag)


3

Fondamentalmente esegue la chiamata del metodo tag.name su ogni tag dell'array.

È una scorciatoia rubino semplificata.


2

Anche se abbiamo già ottime risposte, guardando nella prospettiva di un principiante, vorrei aggiungere ulteriori informazioni:

Cosa significa map (&: name) in Ruby?

Questo significa che stai passando un altro metodo come parametro alla funzione mappa. (In realtà stai passando un simbolo che viene convertito in un proc. Ma questo non è così importante in questo caso particolare).

L'importante è che tu abbia un methodnome nameche verrà usato dal metodo map come argomento invece che dallo blockstile tradizionale .


2

Innanzitutto, &:nameè una scorciatoia per &:name.to_proc, dove :name.to_procrestituisce un Proc(qualcosa di simile, ma non identico a un lambda) che quando chiamato con un oggetto come (primo) argomento, chiama ilname metodo su quell'oggetto.

In secondo luogo, mentre &in def foo(&block) ... endconverte un blocco passato a fooa Proc, fa il contrario quando applicato a a Proc.

Pertanto, &:name.to_procè un blocco che accetta un oggetto come argomento e chiama il namemetodo su di esso, ad es { |o| o.name }.


1

Ecco :nameil simbolo che indica il metodo namedell'oggetto tag. Quando passiamo &:namea map, verrà trattato namecome un oggetto proc. In breve, tags.map(&:name)agisce come:

tags.map do |tag|
  tag.name
end


0

È lo stesso di seguito:

def tag_names
  if @tag_names
    @tag_names
  else
    tags.map{ |t| t.name }.join(' ')
end
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.