Pour combiner des fonctions dans javascript de manière fonctionnelle?

J'apprends la programmation fonctionnelle et je me demande s'il existe un moyen de combiner des fonctions comme ceci:

function triple(x) { return x * 3; } function plusOne(x) { return x + 1; } function isZero(x) { return x === 0; } combine(1); //1 combine(triple)(triple)(plusOne)(1); // 10 combine(plusOne)(triple)(isZero)(-1); // true 

Si le para est une fonction, il "combine" la fonction en elle-même et, sinon, il renverra le résultat final. Merci!

Ce concept vient de Maths agréables. C'est ce qu'on appelle la composition de la fonction .

  f(x) = y g(y) = z g(f(x)) = z (g•f)(x) = z 

Cette dernière ligne est lue "g de f de x est égal à z" . Ce qui explique les fonctions composées est l'élimination des points . Remarque en g(f(x)) = z nous prenons une entrée x et obtenons une sortie z . Cela saute complètement de l'intermédiaire y . Nous disons que nous avons supprimé le point y .

Nous avons donc une fonction composée (g•f)(x) = z , définissons cela en h(x) .

 h(x) = (g•f)(x) = z 

Puisque (g•f) est déjà une fonction, nous pouvons supprimer un autre point, x

 h(x) = (g•f)(x) = z h = (g•f) 

La composition de la fonction est une excellente façon de créer des fonctions d'ordre supérieur et de garder votre code agréable et propre. Il est facile de voir pourquoi nous voulons cela dans votre Javascript.

"Faisons fonctionner notre propre composition!"

D'accord, vous semblez vraiment excité. Et c'est parti…

 function triple(x) { return x * 3; } function plusOne(x) { return x + 1; } function id(x) { return x; } function compN(funcs) { return funcs.reduce(function(g, f) { return function(x) { return g(f(x)); } }, id); } var g = compN([triple, triple, plusOne]); // g(x) = (triple • triple • plusOne)(x) g(1); //=> 18 

Évaluation

 triple(triple(plusOne(1))) triple(triple(2)) triple(6) 18 

Si vous êtes satisfait de cette réponse, bon. Pour ceux qui veulent explorer la construction de meilleures fonctions d'ordre supérieur, procédez comme suit!

En prenant ce concept de créer des fonctions d'ordre supérieur à partir d'autres fonctions, nous pouvons refactoriser et élargir notre définition de compN

Tout d'abord, nous comprenons comment compN évalue les choses. Disons que nous voulons composer 3 fonctions, a , b et c

 // g(x) = a(b(c(x))); // g(x) = (a•b•c)(x) // g = (a•b•c) var g = compN([a,b,c]) 

Maintenant, compN appellera reduce sur le tableau et l'initialiser avec notre fonction d' id (pour des raisons évidentes un peu plus tard)

La façon de réduire les travaux est qu'il appellera cette fonction interne une fois par article dans notre gamme

 function( g, f) { return function(x) { g(f(x)); }; } 

J'ai délibérément ajouté des espaces pour l'aligner sur ce tableau ci-dessous

 iteration gf return explanation ---------------------------------------------------------------------------- #1 id a λx => g(f(x)) original return value λx => id(a(x)) substitute for `g` and `f` a' we'll call this "a prime" #2 a' b λx => g(f(x)) original return value λx => a'(b(x)) substitute for `g` and `f` b' we'll call this "b prime" #3 b' c λx => g(f(x)) original return value λx => b'(c(x)) substitute for `g` and `f` c' we'll call this "c prime" >> c' is the final return value << 

Donc, lorsque nous appelons compN([a,b,c]) , c' est la fonction qui nous est retournée.

"Eh bien, qu'est-ce qui se passe lorsque nous appelons cette fonction avec un argument, comme c'(5) ?"

 alias original x return ---------------------------------------------------------------------------- c'(5); λx => b'(c(x)) 5 b'(c(5)) b'(c(5)) λx => a'(b(x)) c(5) a'(b(c(5))) a'(b(c(5))) λx => id(a(x)) b(c(5)) id(a(b(c(5)))) <-- final return value 

Donc en d'autres termes …

 compN([a,b,c])(5) === id(a(b(c(5)))); 

"C'est frangé, mais c'était un peu difficile pour mon cerveau de suivre."

D'accord, je suis d'accord, et si vous êtes comme moi, quand je retourne à ce code dans 3 mois, je vais me gratter la tête en me demandant ce qu'il fait.

Donc, pour commencer à améliorer compN , soyons d'abord révolutionnaires

 // g(x) = a(b(c(x))); // g(x) = (a•b•c)(x) // g = (a•b•c) var g = compN([a,b,c]) 

Si nous regardons un autre exemple, peut-être que nous allons obtenir un indice

 [1,2,3].reduce(function(x,y) { return x + y; }, 0); //=> 6 

Est le même que

 ((0 + 1) + 2) + 3 //=> 6 

Se cacher au milieu de cette reduce est cette fonction

 function (x,y) { return x + y; } 

Voyez-le? Cela ressemble à une fonction assez simple, n'est-ce pas? Et réutilisable aussi! Si vous pensez, add est un bon nom pour vous, vous auriez raison! Voyons que cela diminue encore

 function add(x,y) { return x + y; } [1,2,3].reduce(add, 0); //=> 6 

C'est aussi très facile à suivre. Je pourrais revenir à ce code à tout moment et savoir exactement ce qui se passe.

Je sais ce que vous pensez

 1 + 2 + 3 

On dirait beaucoup comme

 a • b • c 

"Peut-être si nous extrayons l'itérateur de réduction de compN, nous pouvons simplifier la définition de compN …"

Voici notre compN original

 // original function compN(fs) { return fs.reduce(function(g, f) { return function(x) { return g(f(x)); } }, id); } 

Prenons l'itérateur et appelez-le comp

 function comp(g, f) { return function(x) { return g(f(x)); } } var g = comp(triple)(plusOne); // λx => triple(plusOne(x)) g(1); // triple(plusOne(1)) //=> 6 

OK, alors voyons le compN révisé maintenant

 // revision 1 function compN(fs) { return fs.reduce(comp, id); } 

"Gnarly improvement! Donc nous sommes tous finis maintenant?"

Hahahahah, no.

Regardez que cela reduce simplement assis là. C'est une fonction très utile, et je suis certain que nous pourrions l'utiliser en plusieurs endroits

 function reduce(f, i) { return function(xs) { return xs.reduce(f, i); } } reduce(add, 0)([1,2,3]); //=> 6 reduce(comp, id)([a, b, c]); //=> λx => id(a(b(c(x)))) 

Cette dernière ligne devrait être un indice évident pour la prochaine révision de notre fonction compN

 // revision 2 function compN(fs) { return reduce(comp, id)(fs); } 

"Cela ne semble pas bien mieux que la révision 1 …"

Duh, je sais! Mais sûrement, vous voyez le pendu (fs) à la fin de chaque ligne, n'est-ce pas?

Vous n'écririez pas cela, n'est-ce pas?

 // useless wrapper function max(x) { return Math.max(x); } max(3,1); //=> 3 

Duh! C'est pareil que

 var max = Math.max; max(3,1); //=> 3 

Donc, je présente la récidive finale …

 // recap function reduce(f, i) { return function(xs) { return xs.reduce(f, i); }; } function id(x) { return x; } function comp(g, f) { return function(x) { return g(f(x)); }; } // revision 3 var compN = reduce(comp, id); 

"Et cela fonctionne toujours de la même manière?"

Heck oui, il le fait!

 function triple(x) { return x * 3; } function plusOne(x) { return x + 1; } var g = compN([triple, triple, plusOne]); // λx => id(triple(triple(plusOne(x)))) g(1); // id(triple(triple(plusOne(1)))) //=> 18 

"Mais pourquoi est-ce mieux?"

Eh bien, c'est simple. Bien sûr, nous avons un peu plus de code, mais nous avons 4 fonctions réutilisables maintenant au lieu de seulement 1 . Chaque fonction a une tâche simple qui est facile à identifier immédiatement. Par rapport à la fonction originale, nous finissons avec un code beaucoup plus déclaratif que impératif . Ceci est encore souligné ci-dessous …


Maintenant, pour quelque chose de vraiment, cool. Je ne vais pas plonger trop profond ici, mais ES6 rend cet incroyable pour nous.

 // identical functionality as above let id = x => x; let reduce = (f,i) => xs => xs.reduce(f,i); let comp = (g,f) => x => g(f(x)); let compN = reduce(comp, id); // your functions let triple = x => x * 3; let plusOne = x => x + 1; // try it out! let g = compN([triple, triple, plusOne]); console.log(g(1)); //=> 18 

Allez-y et collez-le dans un REEL Babel pour le voir fonctionner

Et c'est tout, les gens!

Vous pouvez simplement appeler la fonction sur les valeurs de retour elles-mêmes, par exemple:

 plusOne(triple(triple(1))) // 10 isZero(triple(plusOne(-1))) // true 
 function triple(x) { return x * 3; } function plusOne(x) { return x + 1; } function isZero(x) { return x === 0; } var combine = function (v) { var fn = []; function _f(v) { if (typeof v === 'function') { fn.push(v); return _f; } else { return fn.reduce(function (x, f) { return f(x); }, v); } } return _f(v); }; var a, b; console.log(combine(1)); //1 console.log(combine(triple)(triple)(plusOne)(1)); // 10 console.log(combine(plusOne)(triple)(isZero)(-1)); // true console.log(a = combine(plusOne)); // function ... console.log(b = a(triple)); // function ... console.log(b(5)); // 18 console.log(combine(triple)(plusOne)(triple)(plusOne)(triple)(plusOne)(1)); // 40 // @naomik's examples var f = combine(triple); var g = combine(triple)(triple); console.log(f(1)); // 3 console.log(g(1)); // 9 (not 6 as you stated)