Un peu de magie avec to_proc

Publié le 13 janvier 2016 par Martin Catty | back

Cet article est publié sous licence CC BY-NC-SA

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 !

Remontons le temps

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).

Comment ça marche ?

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.

Même les Hash s’y mettent

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.