Accueil / Articles / Démystifier le currying en JavaScript : fonctions fermées, application partielle, réutilisation

Démystifier le currying en JavaScript : fonctions fermées, application partielle, réutilisation

Apprenez comment le currying transforme les fonctions JavaScript à plusieurs arguments en chaînes réutilisables à un seul argument, en quoi il diffère de l’application partielle, et quand il convient de s’en passer.

1051 mots

Si vous continuez à passer le même premier argument à une fonction encore et encore, vous payez une petite taxe de répétition à chaque appel. Le currying vous permet de fixer une partie des entrées d’une fonction une fois et de réutiliser le résultat. Ci-dessous, vous verrez ce qu’est le currying, comment les closures le rendent possible, en quoi il diffère de l’application partielle, et quand il améliore réellement le code.

Que signifie le currying

Le currying transforme une fonction qui prend plusieurs arguments en une séquence de fonctions, chacune prenant exactement un argument. Un appel normal fournit tout en même temps :

f(a, b, c)

La forme curried fournit une valeur par appel, et chaque appel renvoie la fonction suivante dans la chaîne :

f(a)(b)(c)

Chaque fonction intermédiaire se souvient des valeurs des appels précédents. Cette mémoire provient des closures, abordés ci-dessous.

Conversion d’une fonction ordinaire

Commencez avec une addition ordinaire à deux arguments :

function sum(a, b) {
  return a + b;
}

sum(2, 3); // 5

Un petit outil d’aide peut envelopper n’importe quelle fonction à deux arguments : la fonction externe prend la première valeur, renvoie une fonction qui prend la seconde, puis appelle enfin la fonction originale avec les deux valeurs :

function curry(fn) {
  return function (a) {
    return function (b) {
      return fn(a, b);
    };
  };
}

const curriedSum = curry(sum);
curriedSum(2)(3); // 5

Points à noter :

  • curry est de haut ordre : son entrée et sa sortie sont toutes deux des fonctions.
  • Chaque fonction qu’elle renvoie ne s’attend qu’à une seule valeur.
  • La fonction interne peut encore accéder à a après le retour de la fonction externe, car elle en conserve la référence.

Cet outil ne gère que deux paramètres exacts. Les versions polyvalentes vérifient fn.length et continuent à collecter des arguments jusqu’à ce qu’ils soient suffisants, mais le principe reste identique.

Un cas pratique : des générateurs de logs spécialisés

L’arithmétique cache les avantages réels, alors considérez un générateur de logs qui prend un niveau de gravité et un message :

function log(level, message) {
  console.log(`[${level}] ${message}`);
}

Les points d’appel commencent rapidement à répéter le même niveau :

log("ERROR", "Something broke");
log("ERROR", "Invalid token");
log("ERROR", "API failed");

Un logger curried vous permet de créer des loggers prédéfinis une seule fois :

function log(level) {
  return function (message) {
    console.log(`[${level}] ${message}`);
  };
}

const errorLog = log("ERROR");
const infoLog = log("INFO");

errorLog("Something broke");
errorLog("Invalid token");
infoLog("User logged in");

Le niveau est fixé lors de la création de errorLog et infoLog. Les points d’appel deviennent plus courts, l’intention est clairement lisible, et les fonctions spécialisées peuvent être exportées ou injectées ailleurs. Ce mélange de réutilisation et d’expressivité est la raison principale d’utiliser le currying.

Application partielle

L’application partielle consiste à remplir à l’avance certains arguments d’une fonction existante pour obtenir une nouvelle fonction, sans modifier la structure de l’originale. JavaScript le prend en charge nativement grâce à bind :

function multiply(a, b) {
  return a * b;
}

const double = multiply.bind(null, 2);
double(5); // 10

Le premier argument de bind définit this, que multiply ignore, d’où la valeur null. multiply nécessite néanmoins deux arguments ; double n’est qu’une variante où le premier argument est fourni.

Curry versus application partielle

Ces deux concepts partagent un objectif mais pas le même mécanisme :

  • Le currying restructure une fonction en une chaîne de fonctions à un seul argument.
  • L’application partielle remplit certains arguments immédiatement tout en conservant la structure initiale de la fonction.

Chaque étape d’une chaîne curried correspond en fait à une application partielle à un seul argument, mais l’application partielle ne nécessite pas forcément le currying.

Curry ouvert

Une variante amusante continue d’accepter des valeurs jusqu’à ce que la chaîne soit terminée par une appel vide :

let infiniteSum = function (a) {
  return function (b) {
    if (b !== undefined) return infiniteSum(a + b);
    return a;
  };
};

infiniteSum(1)(2)(3)(4)(); // 10

Voici comment cela se déroule :

  • Chaque appel avec une valeur l’ajoute au total en cours et renvoie une nouvelle fonction qui conserve ce total.
  • L’appel final () laisse b à la valeur undefined, ce qui permet de renvoyer la somme totale.
  • Les fermetures de portée transmettent le total d’une étape à l’autre.

Vérifier explicitement que b !== undefined est important. Un test de vérité comme if (b) considérerait 0 comme le terme de fin, de sorte que infiniteSum(1)(0)(2)() s’arrêterait à zéro au lieu de renvoyer 3. Ce schéma constitue un excellent exercice sur les fermetures de portée et une question fréquente lors des entretiens, mais l’appel final facile à oublier en fait un mauvais choix pour des API de production.

Les fermetures de portée permettent tout cela

Voici le plus petit additif curried :

function add(a) {
  return function (b) {
    return a + b;
  };
}

add(3)(4); // 7

add(3) renvoie la fonction interne et s’arrête, mais cette fonction interne peut encore lire a. C’est ce qu’on appelle une fermeture : une fonction conserve l’accès aux variables de son contexte de définition, même après que la fonction externe ait renvoyé. Comme la valeur capturée persiste, il est possible de stocker et de réutiliser cette fonction partiellement appliquée :

const add3 = add(3);

add3(4); // 7
add3(10); // 13

Pour en savoir plus sur la manière dont ces variables capturées sont résolues, consultez comment la chaîne de contextes résout les variables.

Quand l’utiliser et quand l’éviter

Le currying est avantageux lorsque :

  • les mêmes arguments apparaissent fréquemment dans plusieurs appels
  • il rend le code plus clair plutôt que plus obscur
  • vous souhaitez des fonctions petites, composables et réutilisables
  • Vous créez des outils tels que des générateurs de journaux, des validateurs, des formatteurs ou des assistants API, où une valeur de configuration est connue dès le début
  • Sautez cette étape lorsque :

    • une fonction simple et ponctuelle ne gagne rien à être divisée
    • votre équipe n’est pas familière avec ce pattern
    • les appels en chaîne nuisent à la lisibilité

    En résumé

    Le currying consiste essentiellement à organiser les entrées d’une fonction en fonction du moment où elles deviennent connues : les paramètres de configuration tels que le niveau de journalisation ou l’URL de base sont fixés tôt, tandis que les données propres à chaque appel arrivent plus tard. Les closures s’occupent de mémoriser ces informations, bind permet une application partielle plus légère lorsque vous n’avez pas besoin d’une chaîne complète, et la question clé reste toujours de savoir si l’appel du code devient plus facile à lire et à réutiliser.

    Lectures complémentaires

  • Mettre à jour vers htmx 4 : Fetch, héritage explicite et ce qui cesse de fonctionner — Comprendre les changements architecturaux d’htmx 4, du noyau basé sur Fetch et de l’héritage explicite des attributs aux échanges d’erreurs, au morphing et à l’historique, afin de planifier une migration sécurisée.