La programmation fonctionnelle est un style de développement de programme dans lequel certaines fonctionnalités spécifiques pour travailler avec des fonctions sont largement utilisées. Il s'agit, en particulier, de transférer des fonctions vers d'autres fonctions en tant qu'arguments et de renvoyer des fonctions à partir d'autres fonctions. Le concept de «fonctions pures» appartient également au style fonctionnel de programmation. La sortie des fonctions pures ne dépend que de l'entrée, elles, lorsqu'elles sont exécutées, n'affectent pas l'état du programme.
Les principes de programmation fonctionnelle sont pris en charge par de nombreux langages. Parmi eux, JavaScript, Haskell, Clojure, Erlang. L'utilisation de mécanismes de programmation fonctionnelle implique la connaissance, entre autres, de concepts tels que les fonctions pures, les fonctions de curry, les fonctions d'ordre supérieur.

Le matériel que nous traduisons aujourd'hui concerne le curry. Nous allons parler du fonctionnement du curry et de la façon dont la connaissance de ce mécanisme peut être utile à un développeur JS.
Qu'est-ce que le curry?
Curry dans la programmation fonctionnelle est la transformation d'une fonction avec de nombreux arguments en un ensemble de fonctions imbriquées avec un seul argument. Lorsqu'une fonction au curry est appelée avec un argument qui lui est transmis, elle renvoie une nouvelle fonction qui attend l'arrivée du prochain argument. De nouvelles fonctions qui attendent le prochain argument sont renvoyées à chaque appel de la fonction curry - jusqu'à ce que la fonction reçoive tous les arguments dont elle a besoin. Les arguments obtenus précédemment, grâce au mécanisme de fermeture, attendent le moment où la fonction obtient tout ce dont elle a besoin pour effectuer les calculs. Après avoir reçu le dernier argument, la fonction effectue le calcul et renvoie le résultat.
En parlant de
curry , nous pouvons dire que c'est le processus de transformation d'une fonction avec plusieurs arguments en une fonction avec moins d'arité.
L'arité est le nombre d'arguments d'une fonction. Par exemple, voici la déclaration d'une paire de fonctions:
function fn(a, b) { //... } function _fn(a, b, c) { //... }
La fonction
fn
prend deux arguments (c'est une fonction binaire ou 2-aire), la fonction
_fn
prend trois arguments (une fonction ternaire, 3-aire).
Parlons de la situation où, pendant le curry, une fonction avec plusieurs arguments est convertie en un ensemble de fonctions, chacune prenant un argument.
Prenons un exemple. Nous avons la fonction suivante:
function multiply(a, b, c) { return a * b * c; }
Il prend trois arguments et retourne leur produit:
multiply(1,2,3);
Voyons maintenant comment le convertir en un ensemble de fonctions, chacune prenant un argument. Créons une version curry de cette fonction et voyons comment obtenir le même résultat lors de l'appel de plusieurs fonctions:
function multiply(a) { return (b) => { return (c) => { return a * b * c } } } log(multiply(1)(2)(3))
Comme vous pouvez le voir, ici, nous avons converti l'appel en une seule fonction avec trois arguments -
multiply(1,2,3)
en l'appel à trois fonctions -
multiply(1)(2)(3)
.
Il s'avère qu'une fonction s'est transformée en plusieurs fonctions. Lors de l'utilisation de la nouvelle construction, chaque fonction, à l'exception de la dernière, renvoyant le résultat des calculs, prend un argument et renvoie une autre fonction, également capable d'accepter un argument et de renvoyer une autre fonction. Si une construction du formulaire
multiply(1)(2)(3)
ne vous semble pas très claire, écrivons-la sous cette forme pour mieux comprendre ceci:
const mul1 = multiply(1); const mul2 = mul1(2); const result = mul2(3); log(result);
Voyons maintenant ligne par ligne ce qui se passe ici.
Tout d'abord, nous passons l'argument
1
à la fonction
multiply
:
const mul1 = multiply(1);
Lorsque cette fonction fonctionne, cette conception fonctionne:
return (b) => { return (c) => { return a * b * c } }
Maintenant
mul1
a une référence à une fonction qui prend un argument
b
. On appelle la fonction
mul1
, en la passant
2
:
const mul2 = mul1(2);
À la suite de cet appel, le code suivant sera exécuté:
return (c) => { return a * b * c }
La
mul2
contiendra une référence à une fonction qui pourrait s'y trouver, par exemple, à la suite de l'opération suivante:
mul2 = (c) => { return a * b * c }
Si nous appelons maintenant la fonction
mul2
, en lui passant
3
, alors la fonction effectuera les calculs nécessaires en utilisant les arguments
a
et
b
:
const result = mul2(3);
Le résultat de ces calculs sera
6
:
log(result)
La fonction
mul2
, qui a le niveau d'imbrication le plus élevé, a accès à la portée, aux fermetures formées par les fonctions
multiply
et
mul1
. C'est pourquoi dans la fonction
mul2
possible d'effectuer des calculs avec des variables déclarées dans des fonctions dont l'exécution est déjà terminée, qui ont déjà renvoyé quelques valeurs et sont traitées par le garbage collector.
Ci-dessus, nous avons examiné un exemple abstrait, mais, en substance, la même fonction, qui est conçue pour calculer le volume d'une boîte rectangulaire.
function volume(l,w,h) { return l * w * h; } const vol = volume(100,20,90)
Voici à quoi ressemble sa version au curry:
function volume(l) { return (w) => { return (h) => { return l * w * h } } } const vol = volume(100)(20)(90)
Ainsi, le curry est basé sur l'idée suivante: sur la base d'une certaine fonction, une autre fonction est créée qui renvoie une fonction spécialisée.
Curry et utilisation partielle des fonctions
Maintenant, peut-être, on a l'impression que le nombre de fonctions imbriquées, lors de la représentation d'une fonction comme un ensemble de fonctions imbriquées, dépend du nombre d'arguments de la fonction. Et s'il s'agit de curry, c'est le cas.
Une version spéciale de la fonction de calcul du volume, que nous avons déjà vue, peut être réalisée comme suit:
function volume(l) { return (w, h) => { return l * w * h } }
Ici, des idées sont appliquées, très similaires à celles discutées ci-dessus. Vous pouvez utiliser cette fonction comme suit:
const hV = volume(70); hV(203,142); hV(220,122); hV(120,123);
Et vous pouvez le faire:
volume(70)(90,30)
En fait, ici, vous pouvez voir comment nous, avec la commande
volume(70)
, avons créé une fonction spécialisée pour calculer le volume des corps, dont l'une des dimensions (à savoir, la longueur,
l
) est fixe. La fonction de
volume
attend 3 arguments et contient 2 fonctions imbriquées, contrairement à la version précédente d'une fonction similaire, dont la version au curry contenait 3 fonctions imbriquées.
La fonction obtenue après l'appel du
volume(70)
implémente le concept d'une application de fonction partielle. Le curry et l'application partielle des fonctions sont très similaires, mais les concepts sont différents.
En application partielle, la fonction est transformée en une autre fonction avec moins d'arguments (moins d'arité). Certains arguments d'une telle fonction sont fixes (des valeurs par défaut sont définies pour eux).
Par exemple, il existe une telle fonction:
function acidityRatio(x, y, z) { return performOp(x,y,z) }
Il peut être converti en ceci:
function acidityRatio(x) { return (y,z) => { return performOp(x,y,z) } }
L'implémentation de la fonction
performOp()
n'est pas donnée ici, car elle n'affecte pas les concepts considérés.
La fonction qui peut être obtenue en appelant la nouvelle fonction
acidityRatio()
avec un argument dont la valeur doit être fixée est la fonction d'origine, dont l'un des arguments est fixe, et cette fonction elle-même prend un argument de moins que l'original.
La version curry de la fonction ressemblera à ceci:
function acidityRatio(x) { return (y) = > { return (z) = > { return performOp(x,y,z) } } }
Comme vous pouvez le voir, lors du curry, le nombre de fonctions imbriquées est égal au nombre d'arguments de la fonction d'origine. Chacune de ces fonctions attend son propre argument. Il est clair que si la fonction des arguments n'accepte pas, ou n'accepte qu'un seul argument, alors elle ne peut pas être curry.
Dans une situation où une fonction a deux arguments, les résultats de son currying et de son application partielle peuvent être considérés comme coïncidant. Par exemple, nous avons une telle fonction:
function div(x,y) { return x/y; }
Supposons que nous devions le réécrire afin de pouvoir, en fixant le premier argument, obtenir une fonction qui effectue des calculs en ne lui passant que le deuxième argument, c'est-à-dire que nous devons appliquer partiellement cette fonction. Cela ressemblera à ceci:
function div(x) { return (y) => { return x/y; } }
Le résultat de son curry sera exactement le même.
Sur l'application pratique des concepts de curry et d'application partielle de fonctions
Le curry et l'application partielle de fonctions peuvent être utiles dans diverses situations. Par exemple, lors du développement de petits modules pouvant être réutilisés.
Une utilisation partielle des fonctions facilite l'utilisation des modules universels. Par exemple, nous avons une boutique en ligne, dans le code de laquelle il y a une fonction qui est utilisée pour calculer le montant à payer en tenant compte de la remise.
function discount(price, discount) { return price * discount }
Il existe une certaine catégorie de clients, appelons-les «clients bien-aimés», à qui nous offrons une remise de 10%. Par exemple, si un tel client achète quelque chose pour 500 $, nous lui accordons une remise de 50 $:
const price = discount(500,0.10); // $50 // $500 - $50 = $450
Il est facile de remarquer qu'avec cette approche, nous devons constamment appeler cette fonction avec deux arguments:
const price = discount(1500,0.10); // $150 // $1,500 - $150 = $1,350 const price = discount(2000,0.10); // $200 // $2,000 - $200 = $1,800 const price = discount(50,0.10); // $5 // $50 - $5 = $45 const price = discount(5000,0.10); // $500 // $5,000 - $500 = $4,500 const price = discount(300,0.10); // $30 // $300 - $30 = $270
La fonction d'origine peut être réduite à un formulaire qui vous permettrait de recevoir de nouvelles fonctions avec un niveau de remise prédéterminé, sur appel, il suffit de transférer le montant de l'achat. La fonction
discount()
dans notre exemple a deux arguments. Voici à quoi il ressemble dans lequel nous le convertissons:
function discount(discount) { return (price) => { return price * discount; } } const tenPercentDiscount = discount(0.1);
La fonction
tenPercentDiscount()
est le résultat de l'application partielle de la fonction
discount()
. Lors de l'appel à
tenPercentDiscount()
cette fonction, il suffit de passer le prix, et une remise de 10%, c'est-à-dire l'argument
discount
, sera déjà fixée:
tenPercentDiscount(500); // $50 // $500 - $50 = $450
S'il y a des acheteurs dans notre magasin qui ont décidé d'accorder une remise de 20%, vous pouvez obtenir la fonction appropriée pour travailler avec eux comme ceci:
const twentyPercentDiscount = discount(0.2);
Maintenant, la fonction
twentyPercentDiscount()
peut être appelée pour calculer le coût des marchandises, en tenant compte d'une remise de 20%:
twentyPercentDiscount(500); // 100 // $500 - $100 = $400 twentyPercentDiscount(5000); // 1000 // $5,000 - $1,000 = $4,000 twentyPercentDiscount(1000000); // 200000 // $1,000,000 - $200,000 = $600,000
Fonction universelle pour l'application partielle d'autres fonctions
Nous allons développer une fonction qui accepte n'importe quelle fonction et renvoie sa variante, qui est une fonction, dont certains des arguments sont déjà définis. Voici le code qui vous permet de le faire (vous, si vous envisagez de développer une fonction similaire, il est fort possible que vous obteniez autre chose en conséquence):
function partial(fn, ...args) { return (..._arg) => { return fn(...args, ..._arg); } }
La fonction
partial()
accepte la fonction
fn
, que nous voulons convertir en fonction partiellement appliquée, et un nombre variable de paramètres
(...args
). L'instruction
rest
est utilisée pour mettre tous les paramètres après
fn
dans
args
.
Cette fonction renvoie une autre fonction qui accepte également un nombre variable de paramètres (
_arg
). Cette fonction, à son tour, appelle la fonction
fn
origine, transmettez-lui les paramètres
...args
et
..._arg
(en utilisant l'opérateur d'
spread
). La fonction effectue le calcul et renvoie le résultat.
Nous utilisons cette fonction pour créer une variante de la fonction de
volume
vous connaissez déjà, conçue pour calculer le volume de parallélépipèdes rectangulaires, dont l'un des côtés est fixe:
function volume(l,h,w) { return l * h * w } const hV = partial(volume,100); hV(200,900); // 18000000 hV(70,60); // 420000
Vous trouverez ici un exemple de fonction universelle pour le curry d'autres fonctions.
Résumé
Dans cet article, nous avons parlé du curry et de l'application partielle des fonctions. Ces méthodes de transformation de fonctions sont implémentées en JavaScript en raison de fermetures et du fait que les fonctions en JS sont des objets de la première classe (elles peuvent être passées en arguments à d'autres fonctions, renvoyées par elles, affectées à des variables).
Chers lecteurs! Utilisez-vous des techniques de curry et l'application partielle de fonctions dans vos projets?
