Composez partout: composition de fonctions en JavaScript

La traduction de l'article a été préparée spécialement pour les étudiants du cours JavaScript Developer.Basic .










introduction



Il semble que les bibliothèques Lodash et Underscore soient désormais omniprésentes et disposent toujours de la méthode de composition super efficace que nous connaissons aujourd'hui .



Examinons de plus près la fonction de composition et voyons comment elle peut rendre votre code plus lisible, maintenable et élégant.



Les bases



Nous allons couvrir beaucoup de fonctions de Lodash parce que 1) nous n'allons pas écrire nos propres algorithmes de base - cela nous détournera de ce sur quoi je suggère de nous concentrer; et 2) La bibliothèque Lodash est utilisée par de nombreux développeurs et peut être remplacée par Underscore, toute autre bibliothèque ou vos propres algorithmes sans aucun problème.



Avant de regarder quelques exemples simples, examinons exactement ce que fait la fonction de composition et comment implémenter votre propre fonction de composition si nécessaire.



var compose = function(f, g) {
    return function(x) {
        return f(g(x));
    };
};


Il s'agit de la mise en œuvre la plus élémentaire. Notez que les fonctions dans les arguments s'exécuteront de droite à gauche . Autrement dit, la fonction de droite est exécutée en premier et son résultat est passé à la fonction de gauche.



Regardons maintenant ce code:



function reverseAndUpper(str) {
  var reversed = reverse(str);
  return upperCase(reversed);
}


La fonction reverseAndUpper inverse d'abord la chaîne donnée, puis la convertit en majuscules. Nous pouvons réécrire ce code en utilisant la fonction de composition de base:



var reverseAndUpper = compose(upperCase, reverse);


La fonction reverseAndUpper peut maintenant être utilisée:



reverseAndUpper(''); // 


Nous aurions pu obtenir le même résultat en écrivant le code:



function reverseAndUpper(str) {
  return upperCase(reverse(str));
}


Cette option semble plus élégante et est plus facile à entretenir et à réutiliser.



La possibilité de concevoir rapidement des fonctions et de créer des pipelines de traitement de données vous sera utile dans diverses situations lorsque vous devez transformer des données en déplacement. Imaginez maintenant que vous devez passer une collection à une fonction, transformer les éléments de la collection et renvoyer la valeur maximale une fois toutes les étapes du pipeline terminées, ou convertir une chaîne donnée en valeur booléenne. La fonction de composition vous permet de combiner plusieurs fonctions et de créer ainsi une fonction plus complexe.



Implémentons une fonction de composition plus flexible qui peut inclure n'importe quel nombre d'autres fonctions et arguments. La fonction de composition précédente que nous avons examinée ne fonctionne qu'avec deux fonctions et ne prend que le premier argument passé. Nous pouvons le réécrire comme ceci:



var compose = function() {
  var funcs = Array.prototype.slice.call();
 
  return funcs.reduce(function(f,g) {
    return function() {
      return f(g.apply(this, ));
    };
  });
};


Avec une fonction comme celle-ci, nous pouvons écrire du code comme celui-ci:



Var doSometing = compose(upperCase, reverse, doSomethingInitial);
 
doSomething('foo', 'bar');


Il existe de nombreuses bibliothèques qui implémentent la fonction de composition. Nous avons besoin de notre fonction de composition pour comprendre son fonctionnement. Bien sûr, il est implémenté différemment dans différentes bibliothèques. Les fonctions de composition de différentes bibliothèques font la même chose, elles sont donc interchangeables. Maintenant que nous comprenons en quoi consiste la fonction compose, utilisons les exemples suivants pour examiner la fonction _.compose de la bibliothèque Lodash.



Exemples de



Commençons simplement:



function notEmpty(str) {
    return ! _.isEmpty(str);
}


La fonction notEmpty est la négation de la valeur renvoyée par la fonction _.isEmpty.



Nous pouvons obtenir le même résultat en utilisant la fonction _.compose de la bibliothèque Lodash. Écrivons la fonction non:



function not(x) { return !x; }
 
var notEmpty = _.compose(not, _.isEmpty);


Vous pouvez maintenant utiliser la fonction notEmpty avec n'importe quel argument:



notEmpty('foo'); // true
notEmpty(''); // false
notEmpty(); // false
notEmpty(null); // false


Ceci est un exemple très simple. Jetons un coup d'œil à quelque chose d'

un peu plus compliqué: la fonction findMaxForCollection renvoie la valeur maximale d'une collection d'objets avec les propriétés id et val (value).



function findMaxForCollection(data) {
    var items = _.pluck(data, 'val');
    return Math.max.apply(null, items);
}
 
var data = [{id: 1, val: 5}, {id: 2, val: 6}, {id: 3, val: 2}];
 
findMaxForCollection(data);


La fonction de composition peut être utilisée pour résoudre ce problème:



var findMaxForCollection = _.compose(function(xs) { return Math.max.apply(null, xs); }, _.pluck);
 
var data = [{id: 1, val: 5}, {id: 2, val: 6}, {id: 3, val: 2}];
 
findMaxForCollection(data, 'val'); // 6


Il y a du travail à faire ici.



_.pluck attend une collection comme premier argument et une fonction de rappel comme second. Est-il possible d'appliquer partiellement la fonction _.pluck? Dans ce cas, vous pouvez utiliser le currying pour modifier l'ordre des arguments.



function pluck(key) {
    return function(collection) {
        return _.pluck(collection, key);
    }
}


La fonction findMaxForCollection doit être légèrement modifiée . Créons notre propre fonction max.



function max(xs) {
    return Math.max.apply(null, xs);
}


Nous pouvons maintenant rendre notre fonction de composition plus élégante:



var findMaxForCollection = _.compose(max, pluck('val'));
 
findMaxForCollection(data);


Nous avons écrit notre propre fonction pluck et ne pouvons l'utiliser qu'avec la propriété 'val'. Vous ne comprenez peut-être pas pourquoi vous devriez écrire votre propre méthode de récupération si Lodash a déjà une fonction prête à l'emploi et pratique _.pluck. Le problème est qu'il _.pluckattend une collection comme premier argument, et nous voulons le faire différemment. En modifiant l'ordre des arguments, nous pouvons appliquer partiellement la fonction en passant la clé comme premier argument; la fonction retournée acceptera les données.

Nous pouvons affiner un peu plus notre méthode d'échantillonnage. Lodash a une méthode pratique _.curryqui vous permet d'écrire notre fonction comme ceci:



function plucked(key, collection) {
    return _.pluck(collection, key);
}
 
var pluck = _.curry(plucked);


Nous enveloppons simplement la fonction de pincement d'origine pour échanger les arguments. Maintenant, pluck retournera la fonction jusqu'à ce qu'il ait traité tous les arguments. Voyons à quoi ressemble le code final:



function max(xs) {
    return Math.max.apply(null, xs);
}
 
function plucked(key, collection) {
    return _.pluck(collection, key);
}
 
var pluck = _.curry(plucked);
 
var findMaxForCollection = _.compose(max, pluck('val'));
 
var data = [{id: 1, val: 5}, {id: 2, val: 6}, {id: 3, val: 2}];
 
findMaxForCollection(data); // 6


La fonction findMaxForCollection peut être lue de droite à gauche, c'est-à-dire que la propriété val de chaque élément de la collection est d'abord transmise à la fonction, puis elle renvoie le maximum de toutes les valeurs acceptées.



var findMaxForCollection = _.compose(max, pluck('val'));


Ce code est plus facile à maintenir, réutilisable et beaucoup plus élégant. Regardons le dernier exemple, juste pour profiter à nouveau de l'élégance de la composition fonctionnelle.



Prenons les données de l'exemple précédent et ajoutons une nouvelle propriété appelée active. Maintenant, les données ressemblent à ceci:



var data = [{id: 1, val: 5, active: true}, 
            {id: 2, val: 6, active: false }, 
            {id: 3, val: 2, active: true }];




Appelons cette fonction getMaxIdForActiveItems (data) . Il prend une collection d'objets, filtre tous les objets actifs et renvoie la valeur maximale des objets filtrés.



function getMaxIdForActiveItems(data) {
    var filtered = _.filter(data, function(item) {
        return item.active === true;
    });
 
    var items = _.pluck(filtered, 'val');
    return Math.max.apply(null, items);
}


Pouvez-vous rendre ce code plus élégant? Il a déjà les fonctions max et pluck, il suffit donc d'ajouter un filtre:



var getMaxIdForActiveItems = _.compose(max, pluck('val'), _.filter);
 
getMaxIdForActiveItems(data, function(item) {return item.active === true; }); // 5


_.filterLe même problème se pose avec la fonction et avec _.pluck: on ne peut pas appliquer partiellement cette fonction, car elle attend une collection comme premier argument. Nous pouvons changer l'ordre des arguments dans le filtre en encapsulant la fonction initiale:



function filter(fn) {
    return function(arr) {
        return arr.filter(fn);
    };
}


Ajoutons une fonction isActivequi prend un objet et vérifie si l'indicateur actif est défini sur true.



function isActive(item) {
    return item.active === true;
}


Une fonction filteravec une fonction isActivepeut être partiellement appliquée, nous ne passerons donc les données qu'à la fonction getMaxIdForActiveItems .



var getMaxIdForActiveItems = _.compose(max, pluck('val'), filter(isActive));


Il ne nous reste plus qu'à transférer des données:



getMaxIdForActiveItems(data); // 5


Maintenant rien ne nous empêche de réécrire cette fonction pour qu'elle trouve la valeur maximale parmi les objets inactifs et la renvoie:



var isNotActive = _.compose(not, isActive);

var getMaxIdForNonActiveItems = _.compose(max, pluck('val'), filter(isNotActive));


Conclusion



Comme vous l'avez peut-être remarqué, la composition de fonctions est une fonctionnalité intéressante qui rend votre code élégant et réutilisable. L'essentiel est de l'appliquer correctement.



Liens



lodash

Hey Underscore, vous faites le mal! (Hey Underscore, tu fais tout mal!)

@sharifsbeat







Lire la suite:






All Articles