Question Que signifie map (&: name) dans Ruby?


J'ai trouvé ce code dans un RailsCast:

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

Que fait le (&:name) dans map(&:name) signifier?


450
2017-08-01 17:35


origine


Réponses:


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

Si foo est un objet avec un to_proc méthode, alors vous pouvez le transmettre à une méthode comme &foo, qui appellera foo.to_proc et l'utiliser comme bloc de la méthode.

le Symbol#to_proc La méthode a été initialement ajoutée par ActiveSupport mais a été intégrée à Ruby 1.8.7. C'est sa mise en œuvre:

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

471
2017-08-01 17:50



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 Method objet de self cela représente son foo méthode, et utilisé le & pour signifier qu'il a un to_proc  méthode qui le convertit en un Proc.

Ceci est très utile lorsque vous voulez faire des choses sans point style. Un exemple est de vérifier s'il y a une chaîne dans un tableau qui est égal à la chaîne "foo". Il y a la manière conventionnelle:

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

Et il y a la manière sans points:

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

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


159
2018-03-08 18:07



C'est équivalent à

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

70
2017-08-01 17:39



Notons aussi que l'esperluette #to_proc la magie peut fonctionner avec n'importe quelle classe, pas seulement avec le symbole. Beaucoup de rubisistes choisissent 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!"]

Ampersand & fonctionne en envoyant to_proc message sur son opérande, qui, dans le code ci-dessus, est de classe Array. Et depuis que j'ai défini #to_proc méthode sur Array, la ligne devient:

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

39
2017-11-20 12:38



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


35
2017-08-01 17:37



tags.map(&:name)

est le même que

tags.map{|tag| tag.name}

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


24
2017-11-01 03:23



La réponse de Josh Lee est presque correcte sauf que le code Ruby équivalent aurait dû être comme suit.

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, quand 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 obj 1 et args* 'a' pour provoquer une erreur car l'objet Fixnum 1 n'a pas la méthode self (qui est: first).


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

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

  2. map 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. procédure to_proc dans la classe Symbol envoie un message d'envoi à un objet tableau ([1,'a']) avec le paramètre (: premier), par exemple, [1,'a'].send(:first) est exécuté.

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

C'est la même chose que d'exécuter [[1,'a'],[2,'b'],[3,'c']].map(|e| e.first) expression.


14
2018-01-23 21:08



Deux choses se passent ici, et il est important de comprendre les deux.

Comme décrit dans d’autres réponses, le Symbol#to_proc la méthode est appelée.

Mais la raison to_proc est appelé sur le symbole est parce qu'il est passé à map comme argument de bloc. Placement & 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 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 Symbol est converti en un Proc parce qu'il est passé en bloc. Nous pouvons le montrer en essayant de passer un proc à .mapsans 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 bloc. En passant avec & donne .map le bloc attendu.


7
2018-04-09 01:43



(&: name) est l'abréviation de (&: name.to_proc), il est identique à tags.map{ |t| t.name }.join(' ')

to_proc est actuellement implémenté en C


5
2017-08-25 05:50