Ruby has an official certification provided by Ruby Association but it seems to be not so popular. Most of the pages on the official website are in Japanese and you can hardly find any resources on the web on how or why you should take it. In this article, we are sharing the experience of taking ...
Key-Value coding en Ruby — Proc, collections et esperluettes !
Derrière ce curieux titre se cache une technique simple, mais très utile, qui augmente l’expressivité de votre code et préserve votre bonheur de développeur.
Nous découvrirons au passage les différentes manières de passer du code aux méthodes Ruby.
A moins que vous faites toujours du PHP en Ruby, vous n’auriez pas manquer d’utiliser l’une des méthodes de Enumerable sur un tableau.
La plus connue est each, qui traverse une collection avec un bloc sans devoir faire une boucle for ou while “à l’ancienne”. Mais il y a aussi map/collect, reduce/select/reject/find, et un tas d’autres…
Exemple pour obtenir assez sottement la liste des noms des derniers utilisateurs inscrits :
CODE
Une solution plus intelligente consiste à utiliser map pour créer une copie du tableau où chacune des entrées est la résultante de ce que renvoi un bloc dessus :
CODE 2
Une solution plus intelligente encore consiste à utiliser un raccourci bien commode :
CODE 3
Il n’y a rien de spécial à cette syntaxe, si ce n’est une petite particularité native de la classe Symbol.
Le bloc que nous avons passé à map ou each dans les exemples précédents n’est que le dernier paramètre, quelque peu spécial, que prend ces méthodes. Si nous voulons utiliser un bloc défini avant l’appel de each, nous pouvons le passer en paramètre.
CODE
L’esperluette permet de convertir ce qui la succède en un block. N’importe quel object peut être passé à l’esperluette sans soulever d’erreur de syntaxe1. Pour peu que cet object soit “convertible” en proc. L’esperluette ne sait convertir que des proc en blocks, alors elle tente d’appeler sur tout ce qui n’est pas proc la methode… to_proc.2
Et justement, la classe Symbol offre nativement une méthode to_proc qui renvoi un proc
Une implémentation simple en Ruby donnerait ça
CODE
Aller plus loin et augmenter l’expressivité de votre code
Prendre conscience de ce qui vient d’être dit ouvre la voix vers des usages qui peuvent augmenter l’expressivité de votre code.
Exemple, nous voulons collecter les statistiques de plusieurs tenants d’une même application (lien vers tutoriel)
Tenant.active.collect(&Stat)
Comme nous venons de le voir Stat doit renvoyer un proc, ce qui peut être obtenu dans ce cas via une méthode de classe.
stat.rb
Mais on peut faire mieux !
stat.rb
Dans cet exemple on ne fait pas moins que d’extraire la méthode compute de la class Stat, la convertir en un proc, puis à un block (avec l’esperluette) et la mettre à disposition de la méthode collect (alias de map)
Key-Value like-coding
L’utilisation élégante de l’esperluette est très souvent ruinée par la réalité de votre code. Elle est souvent ruinée par le fait de devoir appelée une chaine de deux ou plusieurs 4 méthodes sur chaque élément d’une collection.
Ce serait le cas si nous voulions obtenir, non pas les noms, mais l’emplacement des avatars d’une collection d’utilisateurs…
CODE
On serait tenté d’écrire ça avec un double map
CODE
Ce n’est pas plus mal, mais outre le fait de devoir parcourir deux fois la même collection, la lecture et la compréhension de ce code sont plus difficiles.
Et si nous pouvions exprimé cet appel via la même syntaxe des symbols ? Comme ceci :
CODE
Si on ferme l’oeil sur la nécessité de mettre les noms de méthodes entre quote6, cette syntaxe est indéniablement plus facile à lire.
Son implémentation peut être ainsi faite :
CODE
Elle est compatible avec des appels classiques type collection.map(&:name) tout en permettant d’enchainer les appels de méthodes. Le tout sans eval “evil” ;)
Si vous pensez en avoir souvent l’utilité tout en assumant le microscopique coût en performances à payer, n’hésitez pas à la rajouter à un fichier où vous engloberai toutes vos core_extensions.rb. C’est une bonne pratique qu’il est tout le temps bon de rappeler.
Un mot sur #Inject
#inject est une merveille subtile de programmation fonctionnelle qui permet de traverser une collection tout en gardant la trace du résultat de la traversée passée.
Une parfaite illustration est le calcul de la somme d’une collection
CODE
Sans surprise, cela peut être simplifié à:
CODE
Inject nous signale que les méthodes de Enumerable peuvent passer plus d’un seul paramètre au block. L’opérateur +7 est naturellement binaire et prend donc deux paramètres. Notre surcharge de Symbol#to_proc s’en trouve dans ce cas cassée. Nous devons permettre aux méthodes de passer n’importe quel nombre de paramètre à notre bloc
Voici donc sa version définitive.
CODE
Et lambda dans tout ça ?
Vous avez probablement croisé des blocks de code anonymes créés avec une autre syntaxe : lambda{ }. Qu’en est-t-il. Lambda est une méthode de la class Kernel (disponible partout donc), qui retourne des instance de la class Proc qui se comportent cependant de manière sensiblement différente.
Tout d’abord, les proc créés via lambda sont strictes vis-à-vis de l’arité (la liste de paramètres). Si un proc s’attend à deux paramètres et n’en reçoit qu’un seul, le deuxième est simplement désigné à nil. Dans le cas des lambdas, une exception ArgumentError est soulevée.
Une autre différence, celle-là plus subtile, consiste en une différence de comportement lorsqu’un proc ou lambda englobé dans une méthode classique contient un return explicite. Dans ce cas, proc se comporte comme un bloc et arrête l’exécution de la méthode sus-jacente, tandis que lambda se comporte comme une méthode.
Exemples :
CODE
Notes
1/ Cette syntaxe n’est cependant valable qu’en argument de fonction
2/ Ce comportement est similaire à l’interpolation des chaines de caractère à double guillemets
3/ on peut définir un proc plus simplement via la methode proc de Kernel. Kernel est au sommet de toute la hiérarchie de classe. Donc cette méthode (et bien d’autres) y sont définies. Si vous définissez une méthode dans le scope globale, elle sera attachée à la classe Kernel ! Ruby 1.9 et supérieur offre la syntaxe ->(){} pour créer des procs mais j’évite de l’utiliser dans cet article pour préserver de la lisibilité.
4/ attention tout de même à la loi de Demeter
5/ e pour element, comme i pour item
6/ autrement c’est la méthode path, inexistante qui sera appelée sur l’object :avatar
7/ qui n’est en réalité qu’une méthode de la class Integer (d’où dérivent Fixnum ou Bignum)
Today was an introduction to the course. After about 2.5 hours of orientation, we were paired up and given a list of problems to do. Some of them were already familiar from the prep-work. The problems were focused on drilling common data types in ruby (Arrays, Strings, Hashes, Enumerable). Implementing Towers of Hanoi was fun. This is my first time doing pair programming where one person is the driver and the other the navigator. It was better and more fun than I expected. Pair-programming was surprisingly effective. It was interesting to see how much faster it is to solve a problem when 2 minds work together.
I learned about rubocop incidentally. It's like codequality, but for ruby.
Also, thanks to Ned, I will now think of error message as a murder investigation and stack traces - clues to solving the mystery. I like analogies and I have a feeling that more will be coming.
Problems I worked on in class can be found at https://github.com/vveleva/appacademy/tree/master/w1/w1d1