Object#yield_self (ruby 2.5.0)

Ruby 2.5 introduit une nouvelle méthode du plus haut intérêt : Object#yield_self.

En voici une version (grossièrement) simplifiée :

class Object
  def yield_self
    yield(self)
  end
end

Au premier coup d'œil, ceci ne passe pas pour une fonctionnalité remarquable. Cependant, cette fonctionnalité est très similaire à l'opérateur Pipe d'Elixir ou de F#. Cette fonctionnalité peut sembler similaire à la méthode Object#tap mais le retour de ces méthodes diffère.

Différence avec Object#tap

Commençons par une simple classe d'exemple (Product) :

class Product
  attr_accessor :name, :price, :weight

  def initialize(name)
    @name = name
  end

  def add_delivery_costs
    @price += (@weight > 100 ? 10 : 5)
  end
end

Utilisation de la méthode tap sur une instance de Product :

Product.new('Smartphone X7S').tap do |p|
  p.price = 99.99
  p.weight = 152
  p.add_delivery_costs
end

Retournera une instance de Product :

#<Product:0x007fc4f601fb18 @name="Smartphone X7S", @price=109.99, @weight=152>

L'utilisation de yield_self, retournera le résultat de la dernière méthode utilisée au sein du bloc :

Product.new('Smartphone X7S').yield_self do |p|
  p.price = 99.99
  p.weight = 152
  p.add_delivery_costs
end
109.99

Le résultat est tout sauf surprenant.

Utilisation de Object#yield_self

Syntaxe avant ruby 2.5 :

CSV.parse(File.read(File.expand_path('data.csv'), __dir__))
   .map { |row| row[1].to_i }
   .sum

À partir de ruby 2.5 :

'data.csv'
  .yield_self { |name| File.expand_path(name, __dir__) }
  .yield_self { |path| File.read(path) }
  .yield_self { |body| CSV.parse(body) }
  .map        { |row|  row[1].to_i }
  .sum

Mieux ? Pire ? Il n'y a pas vraiment de réponse catégorique…

On peut trouver divers bénéfices à cette nouvelle syntaxe :

  • une séquence naturelle du haut vers le bas
  • un code plus modifiable, toute nouvelle addition n’altérera pas la lisibilité

On peut aussi souligner divers désavantages :

  • plus verbeux que l'original
  • inhabituel (idiomatique) en Ruby, évidemment puisqu'il s'agit d'une nouvelle fonctionnalité

Un dernier exemple :

require 'uri'
require 'json'
require 'net/http'

'https://api.github.com/repos/ruby/ruby'
  .yield_self { |url| URI.parse(url) }
  .yield_self { |url| Net::HTTP.get(url) }
  .yield_self { |response| JSON.parse(response) }
  .yield_self { |repo| repo.fetch('stargazers_count') }
  .yield_self { |stargazers| "Ruby has #{stargazers} stargazers" }

Conclusion

Object#yield_self est une nouvelle méthode qui sera, certainement, très utile en Ruby 2.5.0 pour réaliser un « pipeline » passant les données d'un bloc vers le suivant. On peut regretter que le nom de la méthode ne soit pas plus concis. Cette fonctionnalité arrive alors qu'une certaine ébullition avait secoué la communauté autour de projets tels que, et sans être exhaustif, piped_ruby, elixirize ou pipe_envy. On peut, vraisemblablement, s'attendre à une rapide adoption puisque la demande semble présente.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Captcha *