Vous le savez, ici on aime bien démystifier les comportements pseudo magiques que nous offrent ruby et rails.
D’ailleurs il est parfois difficile de savoir si un bout de code vient du langage ou du framework.
À tel point qu’on a des quiz dédiés pour tenter de deviner quand il s’agit de ruby ou rails.
Alors, ce snippet, et notamment &:
, ruby ou rails ?
(1..10).map(&:to_s)
Les deux mon général !
Et oui une fois n’est pas coutume, le &:
nous vient de Ruby on Rails mais a été ré-intégré directement dans le langage, à partir de la version 1.8.7.
On en oublierait presque que notre chère version 1.8.6 aurait renvoyé un TypeError: wrong argument type Symbol (expected Proc)
.
Soyons clair, même en 1.8.6, il était possible d’invoquer un bloc de code avec &
, simplement on ne pouvait pas le faire sur un symbole et donc pas par le biais de &:
.
Par exemple, ce code fonctionne parfaitement, même en 1.8.6.
class Hello
def self.to_proc
proc { puts "Hello" }
end
end
(1..10).map(&Hello)
À partir du moment ou un objet est «procable» (si vous avez une version FR je prends en commentaire) on peut l’invoquer de la sorte. Et pour le rendre procable, il suffit de définir to_proc
et le comportement associé.
L’avantage du to_proc
, c’est qu’il transmet automatiquement son contexte :
class Hello
def self.to_proc
proc { |i| puts "Hello from #{i}" }
end
end
(1..10).map(&Hello)
(1..10).map do |index|
Hello.to_proc.call(index)
end
Les deux dernières instructions font la même chose, mais avouez que la première est plus élégante. Dans tous les cas le contenu de notre itérateur est automatiquement passé à la proc.
Ce qui veut dire qu’en rendant un élément de base procable on pourrait très bien appeler dynamiquement des méthodes sur les éléments de l’itérateur.
Imaginons que nous aimerions pouvoir faire ceci :
(1..10).map(&'to_s')
Il suffit de rendre nos string propres. Euh, rendre nos String proc(able).
class String
def to_proc
proc { |context| context.send(self) } # dans notre appel self sera donc 'to_s'
end
end
Voilà, c’est tout ce dont nous avons besoin. Le choix a été fait de rendre procable les symboles plutôt que les String (et c’est mieux ainsi), mais le fonctionnement est strictement identique.
Dans la récente release de Ruby 2.3, les Hash aussi deviennent procable. Rien de très excitant, cela nous permettra de récupérer la valeur depuis la clé.
hash = { a: 1, b: 2, c: 3 }
hash.to_proc.call(:a) #=> 1
L’avantage c’est qu’on pourra également l’invoquer depuis un itérateur :
[:a, :b, :z].map(&hash) #=> [1, 2, nil]
L’équipe Synbioz.
Libres d’être ensemble.