PF: L'application partielle
tags: programmation fonctionnelle
17/09/2017 (c'était un dimanche)
Rappel du sommaire
- Les fonctions en tant que paramètres
- L’immutabilité
- Les fonctions pures
- L’application partielle <--- vous êtes ici
Quelle cuisine!
L’application partielle, est le fait d’appeler une fonction avec une partie de ses paramètres à un instant t, puis le reste des paramètres à un instant t+1. Cela va permettre une mutualisation de code encore plus poussée, et donc moins de répétition.
Un simple sucre syntaxique?
Pour illustrer, prenons tout de suite un exemple
// Sans application partielle
const ajoute3chiffres = (nb1, nb2, nb3) => {
return nb1 + nb2 + nb3;
}
let a = 3;
let b = 5;
// A ce moment là, je ne peux rien faire,
// car j'ai besoin de passer les 3 chiffres en paramètres.
// Beaucoup plus loin dans le code
let c = 7;
let d = 9;
let result1 = ajoute3chiffres(a,b,c);
let result2 = ajoute3chiffres(a,b,d);
Dans cet exemple, on voit qu’il est impossible de faire notre calcul avant d’avoir nos 3 chiffres. De plus, on additionne 2 fois a et b, sans pouvoir réutiliser le résultat, ce qui est dommage.
On aurait pu faire
const ajoute2chiffres = (nb1, nb2) => {
return nb1 + nb2;
}
let result = ajoute2chiffres(ajoute2chiffres(a,b),c);
// si on reprends l'exemple de c, d,
// et qu'on souhaite ne pas refaire a + b
let temp = ajoute2chiffres(a,b);
let result1 = ajoute2chiffres(temp,c);
let result2 = ajoute2chiffres(temp,d);
Mais cela rend la syntaxe lourde lorsqu’on veut additionner 3 ou 4 chiffres, ou cela nous oblige à déclarer une variable temporaire, ce qui n’est jamais bien.
Curry to the rescue
Faire une application partielle pour l’ajout de 3 chiffres revient à
- quand on me passe le premier chiffre, je le garde en mémoire
- quand on me passe le second chiffre, je l’ajoute au premier, et garde la somme en mémoire
- quand on me passe le troisième, je l’additionne à la somme précédente, et je renvoie le tout.
En Javascript, cette fonctionnalité n’existe pas nativement. Il y a 2 solutions:
-
utiliser un helper un peu barbare pour transformer une fonction ‘classique’ en fonction déconstruite. On appelle cela le currying.
const curry = (fx) => { var arity = fx.length; return () => { let args = Array.prototype.slice.call(arguments, 0); if (args.length >= arity) { return fx.apply(null, args); } else { return () => { let args2 = Array.prototype.slice.call(arguments, 0); return f1.apply(null, args.concat(args2)); } } }; }
(Pris sur cet excellent article en anglais)
-
écrire nous-même une fonction déconstruite, pour bien comprendre le fonctionnement.
// Sans application partielle const ajoute3chiffres = (nb1, nb2, nb3) => { return nb1 + nb2 + nb3; }; // Avec application partielle const ajoute3chiffres = (nb1) => { return (nb2) => { // Fonction anonyme, on l'appellera fonction2 let temp = nb1 + nb2; return (nb3) => { // Fonction anonyme, on l'appellera fonction3 return temp + nb3; } } } const aPlusB = ajoute3chiffres(a)(b); // aPlusB est une fonction let result1 = aPlusB(c); let result2 = aPlusB(d);
OH MON DIEU, PLEIN DE FONCTIONS
En effet, ça va être l’exemple un peu plus compliqué de cet article.
ajoute3chiffres
est maintenant une fonction qui ne prend qu’un seul paramètre (nb1
), et qui renvoie une fonction.- cette fonction ne prend qu’un seul paramètre également (
nb2
), fait la somme denb1
etnb2
, et renvoie une nouvelle fonction. - cette dernière fonction prend encore un paramètre (
nb3
), et renvoie la somme finale.
Donc aPlusB
est bien une fonction, qui attend encore un paramètre avant de finaliser le calcul.
Mais elle a déjà calculée a + b
, qui est stocké dans une variable. Cette variable temp
- n’est pas manipulée par le code qui a appelé
ajoute3chiffres
donc cela est transparent dans notre code, - n’est pas modifiable par un autre bout de code, car elle n’existe que dans fonction2, et lorsqu’on reçoit fonction3, on ne peut plus modifier cette variable
Conclusion
L’application partielle consiste donc, en Javascript, à ne faire que des fonctions à un seul paramètre, et à renvoyer des fonctions en retour.
Les principaux gains sont:
- plus besoin de créer de variables temporaires dans du code plus haut, ces variables sont encapsulées dans les fonctions renvoyées,
et ne sont pas modifiables, donc il n’y a plus de risque de remplacer par erreur le résultat de
a+b
, -
avec la syntaxe ES 2017 utilisée au maximum, on peut arriver à du code très compact. J’aurai pu écrire
ajoute3chiffres
sous la forme// Avec application partielle const ajoute3chiffres = nb1 => nb2 => {let temp = nb1 + nb2; return nb3 => temp + nb3;}; // Sans l'optimisation de l'addition const ajoute3chiffres = nb1 => nb2 => nb3 => nb1 + nb2 + nb3;
- dans le cas où certaines opérations nécessitent des requêtes réseaux, on obtient du code qui travaille aussi vite qu’il le peut, sans attendre d’avoir tous les paramètres.