Che cosa significa map (& amp;: name) in Ruby?
-
07-07-2019 - |
Domanda
Ho trovato questo codice in a RailsCast :
def tag_names
@tag_names || tags.map(&:name).join(' ')
end
Cosa significa (& amp;: name)
nella map (& amp;: name)
?
Soluzione
È una scorciatoia per tags.map (& amp;: name.to_proc) .join ('')
Se foo
è un oggetto con un metodo to_proc
, puoi passarlo a un metodo come & amp; foo
, che chiamerà foo.to_proc
e usalo come blocco del metodo.
Il metodo Symbol # to_proc
è 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
Altri suggerimenti
Un'altra bella scorciatoia, sconosciuta a molti, è
array.each(&method(:foo))
che è una scorciatoia per
array.each { |element| foo(element) }
Chiamando il metodo (: foo)
abbiamo preso un oggetto Method
da self
che rappresenta il suo metodo foo
e ha utilizzato & amp;
per indicare che ha un to_proc
metodo che lo converte in Proc
.
Questo è molto utile quando vuoi fare cose senza punto . 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.
È equivalente a
def tag_names
@tag_names || tags.map { |tag| tag.name }.join(' ')
end
Mentre notiamo anche che la e commerciale #to_proc
può funzionare con qualsiasi classe, non solo con Symbol. Molti rubyisti scelgono di definire #to_proc
sulla 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 & amp;
funziona inviando il messaggio to_proc
sul suo operando, che, nel codice sopra, è di classe Array. E poiché ho definito il metodo #to_proc
su Array, la linea diventa:
[ 'Hello', 'Goodbye' ].map { |receiver| receiver.send( :+, ' world!' ) }
È una scorciatoia per tags.map {| tag | tag.name} .join ('')
tags.map(&:name)
è uguale a
tags.map{|tag| tag.name}
& amp;: name
utilizza semplicemente il simbolo come nome del metodo da chiamare.
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 (& amp;: first)
è eseguito, Ruby divide il primo input [1, 'a']
in 1 e 'a' per dare obj
1 e args *
'a 'per causare un errore poiché l'oggetto Fixnum 1 non ha il metodo self (che è: primo).
Quando viene eseguito [[1, 'a'], [2, 'b'], [3, 'c']]. map (& amp;: first)
;
: first
è un oggetto Symbol, quindi quando& amp;: first
viene assegnato a un metodo map come parametro, viene richiamato Symbol # to_proc.-
la mappa invia il messaggio di chiamata a: first.to_proc con il parametro
[1, 'a']
, ad es.: first.to_proc.call ([1, 'a' ])
viene eseguito.
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. -
scorre il resto degli elementi nell'oggetto
[[1, 'a'], [2, 'b'], [3, 'c']]
.
È lo stesso dell'esecuzione di [[1, 'a'], [2, 'b'], [3, 'c']]. map (| e | e.first)
espressione.
Qui stanno accadendo due cose ed è importante capirle entrambe.
Come descritto in altre risposte, viene chiamato il metodo Symbol # to_proc
.
Ma il motivo per cui to_proc
viene chiamato sul simbolo è perché viene passato a map
come argomento di blocco. Posizionare & amp;
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 per map
con 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)
Il Symbol
viene convertito in Proc
perché è passato come blocco. Possiamo mostrarlo provando a passare un proc a .map
senza 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 & amp;
dà a .map
il blocco che si aspetta.
(& amp;: name) è l'abbreviazione di (& amp;: name.to_proc) è uguale a tags.map {| t | t.name} .join ('')
to_proc è effettivamente implementato in C
Anche se abbiamo già ottime risposte, guardando nella prospettiva di un principiante vorrei aggiungere ulteriori informazioni:
Cosa significa map (& amp;: name) in Ruby?
Ciò 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 metodo
chiamato name
che sarà usato dal metodo map come argomento invece del tradizionale blocco
stile.
map (& amp;: name) prende 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)
Qui : name
è il simbolo che punta al metodo name
dell'oggetto tag.
Quando passiamo & amp;: name
a map
, tratteremo name
come un oggetto proc.
In breve, tags.map (& amp;: name)
si comporta come:
tags.map do |tag|
tag.name
end
significa
array.each(&:to_sym.to_proc)
Esegue sostanzialmente la chiamata del metodo tag.name
su ogni tag dell'array.
È una scorciatoia rubino semplificata.
È lo stesso di seguito:
def tag_names
if @tag_names
@tag_names
else
tags.map{ |t| t.name }.join(' ')
end