Go to Hackademy website

Un peu de magie avec to_proc

Martin Catty

Posté par Martin Catty dans les catégories back

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.

Articles connexes

Une brève histoire d'Elixir et Erlang/OTP

31/01/2019

Je développe depuis plusieurs années en Ruby. Depuis mon arrivée chez Synbioz, j’expérimente en plus avec Elixir de façon assez naturelle. En quoi Elixir est-il différent, me demanderez-vous ? Pour...

Écrire une thread pool en Ruby

10/01/2019

Pouvoir exécuter plusieurs tâches en parallèle, que ce soit dans un script ou une application, peut être vraiment très utile, surtout dans le cas où le traitement de ces tâches peut être très long....

Translation temporelle

31/05/2018

Cette semaine, je me suis essayé à un nouveau format d’article qui se présente sous la forme d’une nouvelle de Science-Fiction. Je tiens en passant à remercier Valentin pour ses illustrations....

Authentifier l'accès à vos ressources avec Dragonfly

11/05/2017

Pour ceux qui ne connaissent pas Dragonfly, c’est une application Rack qui peut être utilisée seule ou via un middleware. Le fait que ce soit une application Rack la rend compatible avec toutes les...