Que signifie map (&: name) en Ruby?

496

J'ai trouvé ce code dans un RailsCast :

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

Que signifie l' (&:name)in map(&:name)?

collimarco
la source
122
J'ai d'ailleurs entendu parler de «colon du bretzel».
Josh Lee
6
Haha. Je le sais en tant qu'esperluette. Je n'ai jamais entendu parler de "bretzel" mais cela a du sens.
DragonFax
74
L'appeler "côlon de bretzel" est trompeur, bien qu'entraînant. Il n'y a pas de "&:" en rubis. L'esperluette (&) est un "opérateur d'esperluette unaire" avec un symbole poussé ensemble:. Si c'est quelque chose, c'est un "symbole de bretzel". Je dis juste.
fontno
3
tags.map (&: nom) est une sorte de tags.map {| s | s.name}
kaushal sharma
3
"côlon bretzel" sonne comme une condition médicale douloureuse ... mais j'aime le nom de ce symbole :)
zmorris

Réponses:

517

C'est un raccourci pour tags.map(&:name.to_proc).join(' ')

Si fooest un objet avec une to_procméthode, vous pouvez le passer à une méthode as &foo, qui l'appellera foo.to_procet l'utilisera comme bloc de la méthode.

La Symbol#to_procméthode a été initialement ajoutée 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
Josh Lee
la source
42
C'est une meilleure réponse que la mienne.
Oliver N.
91
tags.map (: name.to_proc) est lui-même un raccourci pour tags.map {| tag | tag.name}
Simone Carletti
5
ce n'est pas un code rubis valide, vous avez toujours besoin du &, c'esttags.map(&:name.to_proc).join(' ')
horseyguy
5
Le symbole # to_proc est implémenté en C, pas en Ruby, mais c'est à cela qu'il ressemblerait en Ruby.
Andrew Grimm
5
@AndrewGrimm a été ajouté pour la première fois dans Ruby on Rails, en utilisant ce code. Il a ensuite été ajouté en tant que fonctionnalité rubis native dans la version 1.8.7.
Cameron Martin
175

Un autre raccourci cool, inconnu de beaucoup, est

array.each(&method(:foo))

qui est un raccourci pour

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

En appelant, method(:foo)nous avons pris un Methodobjet selfqui représente sa foométhode, et avons utilisé le &pour signifier qu'il a une to_proc méthode qui le convertit en a Proc.

C'est très utile lorsque vous voulez faire des choses sans style. Un exemple consiste à vérifier s'il existe une chaîne dans un tableau qui est égale à la chaîne "foo". Il y a la voie conventionnelle:

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

Et il y a la manière sans point:

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

La manière préférée devrait être la plus lisible.

Gerry
la source
25
array.each{|e| foo(e)}est encore plus court :-) +1 quand même
Jared Beck
Pourriez-vous mapper un constructeur d'une autre classe en utilisant &method?
principe holographique
3
@finishingmove ouais je suppose. Essayez ceci[1,2,3].map(&Array.method(:new))
Gerry
78

C'est équivalent à

def tag_names
  @tag_names || tags.map { |tag| tag.name }.join(' ')
end
Sophie Alpert
la source
45

Notons également que la #to_procmagie de l' esperluette peut fonctionner avec n'importe quelle classe, pas seulement avec Symbol. De nombreux Rubyistes choisissent de définir #to_procsur 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 &fonctionne en envoyant un to_procmessage sur son opérande, qui, dans le code ci-dessus, est de classe Array. Et puisque j'ai défini la #to_procméthode sur Array, la ligne devient:

[ 'Hello', 'Goodbye' ].map { |receiver| receiver.send( :+, ' world!' ) }
Boris Stitnicky
la source
C'est de l'or pur!
kubak
38

C'est un raccourci pour tags.map { |tag| tag.name }.join(' ')

Oliver N.
la source
Non, c'est dans Ruby 1.8.7 et supérieur.
Chuck
Est-ce un idiome simple pour la carte ou Ruby interprète-t-il toujours le «&» d'une manière particulière?
collimarco
7
@collimarco: Comme le dit jleedev dans sa réponse, l' &opérateur unaire fait appel to_procà son opérande. Elle n'est donc pas spécifique à la méthode map et fonctionne en fait sur toute méthode qui prend un bloc et transmet un ou plusieurs arguments au bloc.
Chuck
36
tags.map(&:name)

est le même que

tags.map{|tag| tag.name}

&:name utilise simplement le symbole comme nom de méthode à appeler.

Albert.Qing
la source
1
La réponse que je cherchais, plutôt que spécifiquement pour les procs (mais c'était la question des demandeurs)
matrim_c
Bonne réponse! bien clarifié pour moi.
apadana
14

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

ne pas

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

Avec ce code, lorsqu'il print [[1,'a'],[2,'b'],[3,'c']].map(&:first)est exécuté, Ruby divise la première entrée [1,'a']en 1 et «a» pour donner obj1 et args*«a» pour provoquer une erreur car l'objet Fixnum 1 n'a pas la méthode self (qui est: la première).


Quand [[1,'a'],[2,'b'],[3,'c']].map(&:first)est exécuté;

  1. :firstest un objet Symbol, donc quand &:firstest donné à une méthode de carte en tant que paramètre, Symbol # to_proc est invoqué.

  2. la carte envoie un message d'appel à: first.to_proc avec le paramètre [1,'a'], par exemple, :first.to_proc.call([1,'a'])est exécuté.

  3. La procédure to_proc dans 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é.

  4. itère sur le reste des éléments de l' [[1,'a'],[2,'b'],[3,'c']]objet.

Cela revient à exécuter l' [[1,'a'],[2,'b'],[3,'c']].map(|e| e.first)expression.

prosseek
la source
1
La réponse de Josh Lee est absolument correcte, comme vous pouvez le voir en réfléchissant [1,2,3,4,5,6].inject(&:+)- inject attend un lambda avec deux paramètres (mémo et item) et le :+.to_proclivre - Proc.new |obj, *args| { obj.send(self, *args) }ou{ |m, o| m.+(o) }
Uri Agassi
11

Il se passe deux choses ici, et il est important de comprendre les deux.

Comme décrit dans d'autres réponses, la Symbol#to_procméthode est appelée.

Mais la raison to_procpour laquelle le symbole est appelé est qu'il est passé en maptant qu'argument de bloc. Placer &devant un argument dans un appel de méthode le fait passer de cette façon. Cela est vrai pour toute méthode Ruby, pas seulement mappour les 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 Symbolest converti en un Proccar il est transmis en tant que bloc. Nous pouvons le montrer en essayant de passer un proc à .mapsans 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 s'il n'a pas besoin d'être converti, la méthode ne saura pas comment l'utiliser car elle attend un argument de bloc. Le passer avec &donne .maple bloc qu'il attend.

devpuppy
la source
C'est honnêtement la meilleure réponse donnée. Vous expliquez le mécanisme derrière l'esperluette et pourquoi nous nous retrouvons avec un proc, que je n'ai pas obtenu jusqu'à votre réponse. Je vous remercie.
Fralcon
5

(&: nom) est l'abréviation de (&: nom.à_proc) c'est la même chose que tags.map{ |t| t.name }.join(' ')

to_proc est en fait implémenté en C

tessie
la source
5

carte (&: nom) prend un objet énumérable (balises dans votre cas) et exécute la méthode de nom pour chaque élément / balise, produisant 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)

Sunda
la source
3

Il exécute essentiellement l'appel de méthode tag.namesur chaque balise du tableau.

Il s'agit d'un raccourci rubis simplifié.

Olalekan Sogunle
la source
2

Bien que nous ayons déjà d'excellentes réponses, en regardant dans la perspective d'un débutant, j'aimerais ajouter des 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 de carte. (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 un methodnom namequi sera utilisé par la méthode map comme argument au lieu du blockstyle traditionnel .

Jonathan Duarte
la source
2

Tout d'abord, &:nameest un raccourci pour &:name.to_proc, où :name.to_procrenvoie un Proc(quelque chose de similaire, mais pas identique à un lambda) qui, lorsqu'il est appelé avec un objet comme (premier) argument, appelle la nameméthode sur cet objet.

Deuxièmement, alors que &in def foo(&block) ... endconvertit un bloc passé en fooa Proc, il fait le contraire lorsqu'il est appliqué à a Proc.

Ainsi, &:name.to_procest un bloc qui prend un objet comme argument et appelle la nameméthode dessus, c'est-à-dire { |o| o.name }.

Christoph
la source
1

Voici :namele symbole qui pointe vers la méthode namede l'objet tag. Lorsque nous passons &:nameà map, il sera traité namecomme un objet proc. Pour faire court, tags.map(&:name)agit comme:

tags.map do |tag|
  tag.name
end
timlentse
la source
1

ça veut dire

array.each(&:to_sym.to_proc)
mminski
la source
0

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
Naveen Kumar
la source