La curryfication est une technique avancée de travail avec les fonctions. Ce n’est pas seulement utilisé avec JavaScript, mais dans d’autres langages également.
La curryfication est la transformation de fonction qui traduit une fonction de la forme f(a, b, c)
en une fonction de la forme f(a)(b)(c)
.
La curryfication n’appelle pas une fonction ; elle la transforme simplement.
Voyons d’abord un exemple pour mieux comprendre de quoi nous parlons, et mettons ensuite en pratique.
Nous allons créer une fonction d’aide curry(f)
qui curryfie une fonction f
à deux arguments. En d’autres mots, curry(f)
sur une fonction f(a, b)
la traduit en une fonction qui s’appelle par f(a)(b)
:
function curry(f) { // curry(f) fait la curryfication
return function(a) {
return function(b) {
return f(a, b);
};
};
}
// usage
function sum(a, b) {
return a + b;
}
let curriedSum = curry(sum);
alert( curriedSum(1)(2) ); // 3
Comme vous pouvez le voir, l’implémentation est simple : il ne s’agit que de deux enveloppes.
- Le résultat de
curry(func)
est une enveloppefunction(a)
. - Lorsqu’il est appelé comme
curriedSum(1)
, l’argument est sauvegardé dans l’environnement lexical, et une nouvelle enveloppefunction(b)
est retournée. - Ensuite cette enveloppe est appelée avec
2
comme argument, et passe l’appel à la fonction originellesum
.
Des implémentations plus avancées de la curryfication, comme _.curry de la bibliothèque lodash, retournent une enveloppe qui permet à une fonction d’être à la fois appelée normalement et partiellement :
function sum(a, b) {
return a + b;
}
let curriedSum = _.curry(sum); // usage de _.curry de la bibliothèque lodash
alert( curriedSum(1, 2) ); // 3, toujours appelable normalement
alert( curriedSum(1)(2) ); // 3, appelée partiellement
La curryfication ? Pour quoi faire ?
Pour comprendre les bénéfices, nous avons besoin d’un exemple réel.
Par exemple, nous avons la fonction de journalisation log(date, importance, message)
qui formate et écrit l’information. Dans de réels projets de telles fonctions ont beaucoup de fonctionnalités utiles comme envoyer les journaux sur un réseau, ici nous allons juste utiliser alert
:
function log(date, importance, message) {
alert(`[${date.getHours()}:${date.getMinutes()}] [${importance}] ${message}`);
}
Curryfions-la !
log = _.curry(log);
Après ça log
fonctionne normalement:
log(new Date(), "DEBUG", "some debug"); // log(a, b, c)
… mais aussi dans la forme curryfiée :
log(new Date())("DEBUG")("some debug"); // log(a)(b)(c)
Nous pouvons maintenant faire une fonction pratique pour la journalisation actuelle :
// logNow sera la partie partielle de log avec un premier argument fixe
let logNow = log(new Date());
// utilisons-la
logNow("INFO", "message"); // [HH:mm] INFO message
Maintenant logNow
est log
avec un premier argument fixe, en d’autres termes “fonction partiellement appliquée” ou “partielle” pour faire court.
Nous pouvons aller plus loin et faire une fonction pratique pour le débogage actuel :
let debugNow = logNow("DEBUG");
debugNow("message"); // [HH:mm] DEBUG message
Donc :
- Nous n’avons rien perdu après avoir curryfié :
log
est toujours appelable normalement. - Nous pouvons aisément créer des fonctions partielles comme pour la journalisation d’aujourd’hui.
Implémentation avancée de la curryfication
Au cas où vous souhaiteriez entrer dans les détails, voici l’implémentation “avancée” de la curryfication pour les fonctions à plusieurs arguments que nous avons pu utiliser plus haut.
C’est plutôt court :
function curry(func) {
return function curried(...args) {
if (args.length >= func.length) {
return func.apply(this, args);
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2));
}
}
};
}
Exemples d’usage :
function sum(a, b, c) {
return a + b + c;
}
let curriedSum = curry(sum);
alert( curriedSum(1, 2, 3) ); // 6, toujours appelable normalement
alert( curriedSum(1)(2,3) ); // 6, curryfiée au premier argument
alert( curriedSum(1)(2)(3) ); // 6, curryfiée totalement
La nouvelle curry
semble être compliquée, mais est assez simple à comprendre.
Le résultat de curry(func)
est l’enveloppe curried
qui ressemble à ça :
// func est la fonction à transformer
function curried(...args) {
if (args.length >= func.length) { // (1)
return func.apply(this, args);
} else {
return function(...args2) { // (2)
return curried.apply(this, args.concat(args2));
}
}
};
Quand on la lance, il y a deux branches if
:
- Si le nombre d’
args
passé est égal ou supérieur à celui de la fonction d’origine dans sa définition (func.length
), alors on lui passe simplement l’appel en utilisantfunc.apply
. - Sinon, on obtient un partiel : nous n’appelons pas encore
func
. Au lieu de cela, un autre wrapper est retourné, qui réappliqueracurried
en fournissant les arguments précédents avec les nouveaux.
Ensuite, si nous l’appelons, encore une fois, nous obtiendrons soit un nouveau partiel (si pas assez d’arguments) ou, finalement, le résultat.
La curryfaction requiert que la fonction ait un nombre d’arguments fixe.
Une fonction qui utilise un paramètre de reste, comme f(...args)
, ne peut pas être curryfiée de cette façon.
Par définition, la curryfication devrait convertir sum(a, b, c)
en sum(a)(b)(c)
.
Mais la plupart des implémentations en JavaScript sont avancées, comme décrites : elles laissent la possibilité d’appeler la fonction via plusieurs arguments.
Résumé
La curryfication est une transformation qui rend f(a,b,c)
appelable comme f(a)(b)(c)
. Les implémentations en JavaScript laissent généralement la possibilité d’appeler les fonctions normalement et de retourner une partielle si le nombre d’arguments n’est pas suffisant.
La curryfication nous permet d’avoir aisément des partielles. Comme nous avons pu le voir dans l’exemple de journalisation, après avoir curryfié la fonction à trois arguments, log(date, importance, message)
nous donne une partielle quand appelée avec un argument (comme log(date)
) ou deux arguments (comme log(date, importance)
).