Ruby idiomatique #2

Dans le précédent billet, on était arrivé à un résultat plus efficace (et plus élégant !) en utilisant #map au lieu de #each. Pour rappel cela donnait ça :

def values_to_strings(array)  
  array.map do |value|
      value.to_s
  end
end  

On peut faire mieux, plus concis et tout aussi expressif. On pourrait d’abord imaginer utiliser l’écriture suivante :

def values_to_strings(array)  
  array.map { |value| value.to_s }
end  

C’est effectivement mieux mais lorsque tout ce que fait notre itérateur est d’appeler une méthode sur l’objet sur lequel on itère, on peut alors utiliser ce raccourci :

def values_to_strings(array)  
  array.map(&:to_s)
end  

Pas mal non ? ;)

L’astuce réside encore une fois dans la connaissance et la compréhension de Ruby. La méthode #map prend comme tout itérateur un bloc en entrée. Il faut savoir qu’on peut très bien définir un Proc et le passer directement à notre itérateur. En mettant un & devant, on indique que l’argument derrière est un bloc et qu’il répondra donc à #call.

On peut le vérifier de cette manière :

array = [1, 2, 3, 4, 5]  
multiplier = -> (number) { number * 2 }  
multiplier.call(3) # => 6  
array.map(&multiplier) # => [2, 4, 6, 8, 10]  

Tout ça c’est très bien mais on va me répondre que l’exemple plus haut n’utilise pas un Proc ni un lambda mais un Symbol ! Et en effet, il y a une dernière subtilité qui permet de faire fonctionner tout ça. En passant un Symbol en tant que bloc, une conversion implicite aura lieu qui va appeler la méthode #to_proc sur notre Symbol.
Un Symbol converti en Proc prendra comme argument un objet, n’importe lequel, et appellera dessus la méthode ayant pour nom le Symbol défini. Petit exemple :

array = [1, 2, 3, 4, 5]  
size_proc = :size.to_proc  
size_proc.call(array) # => 5  

Et voilà ! C’est comme si on avait directement appelé #size sur array. Pratique non ? :)