Que signifie map (& name) en Ruby?
-
07-07-2019 - |
Question
J'ai trouvé ce code dans un RailsCast :
def tag_names
@tag_names || tags.map(&:name).join(' ')
end
Que signifie (& amp;: name)
dans map (& amp;: name)
?
La solution
C'est un raccourci pour tags.map (& amp;: name.to_proc) .join ('')
Si foo
est un objet avec une méthode to_proc
, vous pouvez le transmettre à une méthode sous la forme & amp; foo
, qui appellera foo.to_proc
et utilisez-le comme bloc de la méthode.
La méthode Symbol # to_proc
a été ajoutée à l'origine par ActiveSupport mais a été intégrée à Ruby 1.8.7. Voici sa mise en œuvre:
class Symbol
def to_proc
Proc.new do |obj, *args|
obj.send self, *args
end
end
end
Autres conseils
Un autre raccourci intéressant, inconnu de beaucoup, est
array.each(&method(:foo))
qui est un raccourci pour
array.each { |element| foo(element) }
En appelant la méthode (: foo)
, nous avons pris un objet Méthode
de self
qui représente sa méthode foo
. , et a utilisé le & amp;
pour indiquer qu'il possède un to_proc
méthode qui la convertit en Proc
.
Ceci est très utile lorsque vous souhaitez appliquer un style sans points . Un exemple consiste à vérifier si un tableau contient une chaîne égale à la chaîne " foo " <
. Il y a la manière conventionnelle:
["bar", "baz", "foo"].any? { |str| str == "foo" }
Et il y a la méthode sans points:
["bar", "baz", "foo"].any?(&"foo".method(:==))
La méthode préférée devrait être la plus lisible.
Cela équivaut à
def tag_names
@tag_names || tags.map { |tag| tag.name }.join(' ')
end
Bien que nous remarquions également que l'esperluette #to_proc
peut fonctionner avec n'importe quelle classe, pas seulement avec Symbol. De nombreux rubyists ont choisi de définir #to_proc
sur la classe Array:
class Array
def to_proc
proc { |receiver| receiver.send *self }
end
end
# And then...
[ 'Hello', 'Goodbye' ].map &[ :+, ' world!' ]
#=> ["Hello world!", "Goodbye world!"]
esperluette & amp;
fonctionne en envoyant un message to_proc
sur son opérande, qui, dans le code ci-dessus, appartient à la classe Array. Et depuis que j'ai défini la méthode #to_proc
sur Array, la ligne devient:
[ 'Hello', 'Goodbye' ].map { |receiver| receiver.send( :+, ' world!' ) }
C'est un raccourci pour tags.map {| tag | tag.name} .join ('')
tags.map(&:name)
est identique à
tags.map{|tag| tag.name}
& amp;: name
utilise simplement le symbole comme nom de méthode à appeler.
La réponse de Josh Lee est presque correcte, sauf que le code Ruby équivalent aurait dû être le suivant.
class Symbol
def to_proc
Proc.new do |receiver|
receiver.send self
end
end
end
pas
class Symbol
def to_proc
Proc.new do |obj, *args|
obj.send self, *args
end
end
end
Avec ce code, lorsque affiche [[1, 'a'], [2, 'b'], [3, 'c']]. map (& amp;: first)
est exécuté, Ruby divise la première entrée [1, 'a']
en 1 et 'a' pour donner obj
1 et args *
'a 'provoquer une erreur car l'objet Fixnum 1 n'a pas la méthode self (qui est: first).
Lorsque [[1, 'a'], [2, 'b'], [3, 'c']]. map (& amp;: first)
est exécuté;
-
: first
est un objet Symbol. Ainsi, lorsque& amp;: first
est donné à une méthode map en tant que paramètre, Symbol # to_proc est appelé. -
map envoie le message d'appel à: first.to_proc avec le paramètre
[1, 'a']
, par exemple,: first.to_proc.call ([1, 'a' ])
est exécuté. -
La procédure to_proc de la classe Symbol envoie un message d'envoi à un objet tableau (
[1, 'a']
) avec le paramètre (: first), par exemple,[1, 'a']. send (: first)
est exécuté. -
itère sur le reste des éléments de l'objet
[[1, 'a'], [2, 'b'], [3, 'c']]
.
Cela revient à exécuter [[1, 'a'], [2, 'b'], [3, 'c']]. map (| e | e.first)
expression.
Deux choses se passent ici et il est important de comprendre les deux.
Comme décrit dans d'autres réponses, la méthode Symbol # to_proc
est appelée.
Mais si to_proc
est appelé sur le symbole, c'est parce qu'il est transmis à map
en tant qu'argument de bloc. Si vous placez & amp;
devant un argument dans un appel de méthode, il est transmis de cette façon. Ceci est vrai pour toutes les méthodes Ruby, pas seulement map
avec des symboles.
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)
Le Symbole
est converti en un Proc
car il est transmis sous forme de bloc. Nous pouvons montrer cela en essayant de passer un proc à .map
sans l'esperluette:
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"]
Même si elle n'a pas besoin d'être convertie, la méthode ne saura pas comment l'utiliser car elle attend un argument de blocage. Le passer avec & amp;
donne à .map
le bloc attendu.
(& amp;: name) est l'abréviation de (& amp;: name.to_proc), il est identique à tags.map {| t | t.name} .join ('')
to_proc est réellement implémenté en C
Bien que nous ayons déjà de bonnes réponses, en regardant à travers la perspective d'un débutant, j'aimerais ajouter les informations supplémentaires:
Que signifie map (& name) en ruby?
Cela signifie que vous transmettez une autre méthode en tant que paramètre à la fonction map. (En réalité, vous passez un symbole qui est converti en proc. Mais ce n’est pas si important dans ce cas particulier.)
Ce qui est important, c’est que vous ayez une méthode
nommée nom
qui sera utilisée par la méthode map comme argument au lieu du bloc traditionnel
style.
map (& amp;: name) prend un objet énumérable (balises dans votre cas) et exécute la méthode name pour chaque élément / balise, en générant chaque valeur renvoyée par la méthode.
C’est un raccourci pour
array.map { |element| element.name }
qui retourne le tableau des noms d'élément (tag)
Ici : nom
est le symbole qui pointe vers la méthode nom
de l'objet tag.
Lorsque nous passons & amp;: name
à map
, il traitera name
comme un objet proc.
Pour résumer, tags.map (& amp;: name)
agit comme:
tags.map do |tag|
tag.name
end
cela signifie
array.each(&:to_sym.to_proc)
Il exécute en gros l'appel de méthode tag.name
sur chaque balise du tableau.
C’est un raccourci rubis simplifié.
C'est la même chose que ci-dessous:
def tag_names
if @tag_names
@tag_names
else
tags.map{ |t| t.name }.join(' ')
end