Publié par
Il y a 2 années · 5 minutes · Front

Ramda : le nouveau lodash

Qu'est ce que Ramda ?Vous utilisez Lodash ? Aujourd’hui c’est devenu l’outil indispensable lorsque l’on fait du javascript. Ramda va encore plus loin dans l’approche de développement fonctionnel : http://ramdajs.com/. Cet article a pour but de vous présenter cette bibliothèque, ses spécificités et en quoi elle peut être utile.

Qu’est-ce que Ramda ?

Ramda est une bibliothèque fonctionnelle Javascript, comme il en existe déjà ; cependant elle permet d’aller plus loin dans son approche fonctionnelle du fait de la combinaison de deux caractéristiques :

  • les méthodes sont automatiquement curryfiées (curried) ;
  • les méthodes prennent les données en dernier paramètre.

Ramda en détails

Qu’est-ce qu’une méthode curryfiée ?

Fonction curryfiée

Prenons une fonction à n arguments, exemple pour n = 4 :

var fctInitiale = function(a, b, c, d) {
 return [a, b, c, d];
};

À laquelle, on applique curry avec Ramda :

 var curriedFct1 = R.curry(fctInitiale);

curriedFct1 est fonctionnellement identique à fctInitiale, à la différence qu’elle peut prendre de 1 à n paramètres d’entrée. Si la totalité des paramètres de fctInitiale (soit les n valeurs) n’a pas été fournie, curriedFct1 retourne une nouvelle fonction curryfiée ayant pour signature les arguments restants. L’exemple suivant est plus parlant :

var curriedFct2 = curriedFct1(4);

En appelant curriedFct1 avec 1 seul argument, elle retourne une nouvelle fonction prenant en paramètres (n – 1 =) 3 arguments, ce qui est fonctionnellement équivalent à écrire ceci : (attention, il ne s’agit pas de la manière dont ramda l’implémente)

var curriedFct2 = R.curry(
 function(b, c, d) {
  var a = 4;
  return fctInitiale(a, b, c, d);
 }
);

curriedFct2 est également currifiée, donc on peut l’appeler ainsi :

var curriedFct3 = curriedFct2(3, 2);

Ce qui est fonctionnellement équivalent à écrire :

var curriedFct3 = R.curry(
 function(d) {
  var a = 4;
  var b = 2;
  var c = 3;
  return fctInitiale(a, b, c, d);
 }
);

curriedFct3 n’attend plus qu’un argument, on obtient donc le résultat en l’appelant ainsi :

curriedFct3(1); //=> [4, 3, 2, 1]

Ce qui est équivalent au résultat de :

fctInitiale(4, 3, 2, 1); //=> [4, 3, 2, 1]

Ou bien de :

curriedFct1(4, 3, 2, 1); //=> [4, 3, 2, 1]

Ou encore de :

curriedFct2(3, 2, 1); //=> [4, 3, 2, 1]

Pourquoi prendre les données en dernier paramètre ?

L’API est dite « function-first, data-last », et comme vu précédemment les fonctions dans ramda sont toutes curryfiées. Ces deux caractéristiques permettent de composer des fonctions pour répondre au besoin en ne renseignant les données qu’à la toute fin.

Exemple simple

La fonction contains de Ramda prend deux arguments, un élément, et la liste dans laquelle on souhaite savoir s’il est présent. En renseignant le premier, on construit une nouvelle fonction :

var containsValue3 = R.contains(3);

Cette nouvelle fonction a pour paramètre la liste dans laquelle on cherche l’élément ‘3’ :

containsValue3([2, 4, 5, 3]); //=> true
containsValue3([1, 2]); //=> false
Autre exemple

Pour mieux comprendre l’intérêt par rapport à Lodash. Voici l’utilisation de la fonction filter :

var nonRendus = _.filter(tasks, {rendu: false});
var nonRendus = R.filter(R.where({rendu: R.equals(false)}));

La différence devient clair, Lodash prend en paramètre les données, et renvoi le résultat. Ramda renvoi une fonction, cette fonction prend en paramètre les données et peut être facilement réutilisée :

nonRendus(tasks);
nonRendus(autreListe);

Avant de vous proposez un exercice, je souhaite présenter une fonction qui est au cœur du développement fonctionnel, c’est la composition : R.compose avec Ramda. Elle permet d’associer deux fonctions en utilisant le résultat de l’une comme paramètre de la seconde :

Composition : R.compose

Prenons la liste suivante :

var list = [2, 4, 1, 4, 3, 8, 7, 9];

Simplement pour l’exemple, disons que l’on souhaite incrémenter de 1 les nombres impairs, et supprimer les doublons.

var plus1AuxImpairs = R.map(function(el){
  if( el%2 === 1 ) {
    return ++el;
  }
  else {
    return el;
  }
});

Avec R.compose, la fonction de droite est appelée en premier. Son résultat sert de paramètre à la fonction de gauche :

var composition = R.compose(R.uniq, plus1AuxImpairs);

Ce qui donne :

composition(list); //=> [2, 4, 8, 10]
Exercice

Prenons le fichier JSON suivant : livres.json. Il contient les réservations de livres d’une bibliothèque. La responsable demande d’obtenir la liste des utilisateurs associés aux livres réservés, non retournés, ordonnés par date de retour descendante.

Cherchons d’abord à récupérer les livres non retournés. Ramda permet d’utiliser la fonction filter sous la forme d’une requête. On souhaite obtenir les données pour lesquels la valeur retour est à false.

var nonRendus = R.filter(R.where({rendu: R.equals(false)}));

On obtient bien le premier résultat attendu, en appelant :

nonRendus(livres);

Ensuite, trions les livres par date de retour, on peut combiner les fonctions, pour obtenir un tri descendant : (Rq : la composition utilise le résultat de la fonction de gauche pour alimenter la fonction de droite, ici on ordonne par date ascendante, puis on inverse l’ordre)

var ordonneParDateRetour = R.sortBy(R.prop('dateRetour'));
var ordonneParDateRetourDesc = R.compose(R.reverse, ordonneParDateRetour)

Nous allons maintenant regrouper par utilisateur :

var groupeParUtilisateur = R.groupBy(R.prop('utilisateur'));

À partir de ces différentes fonctions, nous pouvons faire de nouvelles combinaisons :

var livresNonRendusParUtilisateur = R.compose(groupeParUtilisateur, nonRendus);

Et ainsi créé la fonction résultat : (Rq : ici la fonction R.map va appliquer à chaque liste de « livres (non rendus) groupés par utilisateur », la fonction ordonneParDateRetourDesc)

var resultat = R.compose(R.map(ordonneParDateRetourDesc), livresNonRendusParUtilisateur);

La fonction obtenue donne le résultat en prenant en paramètre la liste initiale :

var solution = resultat(liste);

Adopter Ramda ?

L’avantage est donc la possibilité de construire des fonctions réutilisables. Cependant quelques réticences existent encore :

  • L’API n’a pas atteint la version 1, elle reste encore instable et évolue régulièrement.
  • Les performances actuelles sont inférieures à celles de Lodash, vous pourrez trouver les résultats des tests sur JSPerf.
  • Il est difficile de debugger les méthodes fonctionnelles construites.

Conclusion

Ramda est très fonctionnel, et permet d’écrire et réutiliser très simplement des fonctions complexes. Aujourd’hui les performances de Lodash restent meilleures que celles de Ramda. Les deux bibliothèques peuvent coexister dans un projet, leur utilisation est complémentaire. Ramda n’est pas encore très mature, son API évolue, et ses performances s’amélioreront.

Arthur Sudre
Développeur Full-Stack js, le javascript est ma passion, et je la partage avec plaisir.

5 réflexions au sujet de « Ramda : le nouveau lodash »

  1. Publié par elnazi, Il y a 2 années

    Du Javascript : beurk ;)

  2. Publié par matutine, Il y a 2 années

    Si t’aime Ramda, tu devrais adorer LiveScript non ?! http://livescript.net et sa librairie Prelude, qui rend le tout fonctionnel.

  3. Publié par buzzdecafe, Il y a 1 année

    Merci pour les mots gentils. Je suis un auteur de Ramda. (S’il vous plaît pardonnez mon français.)

    `var nonRendus = R.filter(R.where({rendu: R.equals(false)}));`

    pourrait également être fait

    `var nonRendus = R.filter(R.whereEq({rendu: false}));`

  4. Publié par Arthur Sudre, Il y a 1 année

    Merci de t’être intéressé à mon article.
    Et merci pour la correction, je note ça :)

  5. Publié par buzzdecafe, Il y a 1 année

    Pas une correction; une alternative. Le vôtre est aussi correct.

Laisser un commentaire

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