PC & Mobile

Expérimentons avec des générateurs fonctionnels et l’opérateur de pipeline en JavaScript

Expérimentons avec des générateurs fonctionnels et l’opérateur de pipeline en JavaScript


Photo par Patrick Hendry sur Unsplash
Un générateur est une fonction qui renvoie la valeur suivante de la séquence à chaque appel.

Commençons par un simple générateur fonctionnel qui donne le prochain entier chaque fois que l’on appelle. Cela commence à 0.

séquence de fonctions () {
laisser compter = 0;
fonction de retour () {
const result = count;
compte + = 1;
retourne le résultat;
}
}
const nextNumber = sequence ();
nextNumber (); // 0
nextNumber (); //1
nextNumber (); // 2

nextNumber () est un générateur infini. nextNumber () est également une fonction de fermeture.

Générateur fini

Les générateurs peuvent être finis. Vérifiez le prochain exemple où séquence() crée un générateur qui renvoie des nombres consécutifs à partir d'un intervalle spécifique. A la fin de la séquence, il retourne indéfini:

séquence de fonctions (de, à) {
laisser compter = de;
fonction de retour () {
si (compte <à) {
const result = count;
compte + = 1;
retourne le résultat;
}
}
}
const nextNumber = sequence (10, 15);
nextNumber (); //dix
nextNumber (); // 12
nextNumber (); // 13
nextNumber (); // 14
nextNumber (); //indéfini

lister()

Lorsque vous travaillez avec des générateurs, nous pouvons vouloir créer une liste avec toutes les valeurs de la séquence. Pour cette situation, nous avons besoin d'une nouvelle fonction lister() cela prend un générateur et renvoie toutes les valeurs de la séquence sous forme de tableau. La séquence devrait être finie.

fonction toList (sequence) {
const arr = [];
let value = sequence ();
while (valeur! == non définie) {
arr.push (valeur);
valeur = séquence ();
}
retour arr;
}

Utilisons-le avec le générateur précédent.

nombres const = toList (sequence (10, 15));
//[10,11,12,13,14]

L'opérateur de pipeline

L'opérateur de pipeline |> nous permet d’écrire des transformations de données de manière plus expressive. L'opérateur de pipeline fournit des sucres syntaxiques sur les appels de fonction avec un seul argument. Considérons le prochain code:

const shortenText = shortenText (majuscule ("Ceci est un texte long"));
fonction capitalize (text) {
return text.charAt (0) .toUpperCase () + text.slice (1);
}
fonction shortenText (text) {
return text.substring (0, 8) .trim ();
}

Avec l'opérateur de pipeline, la transformation peut être écrite comme suit:

const shortenText = "C'est un long texte" 
|> capitaliser
|> shortenText;
//C'est

En ce moment, l'opérateur du pipeline est expérimental. Vous pouvez l'essayer avec Babel:

  • dans package.json fichier ajouter le plugin babel pipeline:
{
"dépendances": {
"@ babel / plugin-syntax-pipeline-operator ":" 7.2.0 "
    }
}
  • dans le .babelrc fichier de configuration ajouter:
{
"plugins":[["[["@ babel / plugin-proposition-pipeline-opérateur ", {
"proposition": "minimal"}]]
}

Générateurs sur les collections

Rendre votre code plus facile à lire avec la programmation fonctionnelle, j’avais un exemple de traitement d’une liste de todos . Voici le code:

fonction isPriorityTodo (tâche) {
return task.type === "RE" &&! task.completed;
}
fonction toTodoView (tâche) {
return Object.freeze ({id: task.id, desc: task.desc});
}
const filterTodos = todos.filter (isPriorityTodo) .map (toTodoView);

Dans cet exemple, le todos La liste passe par deux transformations. Une liste filtrée est d'abord créée, puis une deuxième liste avec les valeurs mappées est créée.

Avec les générateurs, nous pouvons effectuer les deux transformations et créer une seule liste. Pour cela, nous avons besoin d'un générateur séquence() cela donne la valeur suivante d'une collection.

séquence de fonctions (liste) {
laisser index = 0;
fonction de retour () {
if (index <list.length) {
const result = list[index];
indice + = 1;
retourne le résultat;
}
};
}

filtrer () et carte ()

Ensuite, nous avons besoin de deux décorateurs filtre() et carte(), qui fonctionnent avec des générateurs fonctionnels.

filtre() prend un générateur et crée un nouveau générateur qui ne renvoie que les valeurs de la séquence qui satisfait la fonction de prédicat.

carte() prend un générateur et crée un nouveau générateur qui renvoie la valeur mappée.

Voici les implémentations:

filtre de fonction (prédicat) {
fonction de retour (séquence) {
retourne la fonction filterSequence () {
valeur const = séquence ();
if (valeur! == non définie) {
if (prédicat (valeur)) {
valeur de retour;
} autre {
return filterSequence ();
}
}
};
};
}
fonction map (mapping) {
fonction de retour (séquence) {
fonction de retour () {
valeur const = séquence ();
if (valeur! == non définie) {
mappage de retour (valeur);
}
};
};
}

Je voudrais utiliser ces décorateurs avec l'opérateur de pipeline. Donc, au lieu de créer filtre (séquence, prédicat) {} avec deux paramètres, j’en ai créé une version curry qui sera utilisée comme ceci: filtre (prédicat) (séquence). De cette façon, cela fonctionne bien avec l'opérateur du pipeline.

Maintenant que nous avons la boîte à outils, faite de séquence, filtre, carte et lister fonctions, pour travailler avec des générateurs sur des collections, nous pouvons toutes les mettre dans un module ("./séquence"). Voir ci-dessous comment réécrire le code précédent à l'aide de cette boîte à outils et de l'opérateur de pipeline:

importer {séquence, filtrer, mapper, prendre, toList} depuis "./sequence";
const filterTodos =
séquence (todos)
|> filtre (isPriorityTodo)
|> carte (toTodoView)
|> toList;

réduire()

Prenons un autre exemple qui calcule le prix des fruits à partir d’une liste de courses.

fonction addPrice (totalPrice, line) {
return totalPrice + (line.units * line.price);
}
fonction areFruits (line) {
return line.type === "FRT";
}
laisser fruitsPrice = shoppingList.filter (areFruits) .reduce (addPrice, 0);

Comme vous pouvez le constater, nous devons d’abord créer une liste filtrée, puis calculer le total de cette liste. Réécrivons le calcul avec des générateurs fonctionnels et évitons la création de la liste filtrée.

Nous avons besoin d'une nouvelle fonction dans la boîte à outils: réduire(). Il prend un générateur et réduit la séquence à une seule valeur.

function réduire (accumulateur, startValue) {
fonction de retour (séquence) {
let result = startValue;
let value = sequence ();
while (valeur! == non définie) {
résultat = accumulateur (résultat, valeur);
valeur = séquence ();
}
retourne le résultat;
};
}

réduire() a exécution immédiate.

Voici le code réécrit avec les générateurs:

importer {séquence, filtrer, réduire} de "./sequence";
const fruitsPrice = séquence (shoppingList) 
|> filtre (areFruits)
|> réduire (addPrice, 0);

prendre()

Un autre scénario courant consiste à ne prendre que le premier n éléments d'une séquence. Pour ce cas, nous avons besoin d'un nouveau décorateur prendre(), qui reçoit un générateur et crée un nouveau générateur qui ne retourne que le premier n éléments de la séquence.

fonction take (n) {
fonction de retour (séquence) {
laisser compter = 0;
fonction de retour () {
si (compte <n) {
compte + = 1;
séquence de retour ();
}
};
};
}

Encore une fois, ceci est la version au curry de prendre() ça devrait s'appeler comme ça: prendre (n) (séquence).

Voici comment vous pouvez utiliser prendre() sur une suite infinie de nombres:

importer {séquence, toList, filtrer, prendre} de "./sequence";
fonction isEven (n) {
retourne n% 2 === 0;
}
const first3EvenNumbers = séquence()  
|> filtre (isEven)
|> prendre (3)
|> toList;

//[0, 2, 4]

Générateurs personnalisés

Nous pouvons créer n’importe quel générateur personnalisé et l’utiliser avec la boîte à outils et l’opérateur de pipeline. Créons le générateur personnalisé Fibonacci:

fonction fibonacciSequence () {
Soit a = 0;
soit b = 1;
fonction de retour () {
const aResult = a;
a = b;
b = aRésultat + b;
retourne un résultat;
};
}
const fibonacci = fibonacciSequence ();
fibonacci ();
fibonacci ();
fibonacci ();
fibonacci ();
fibonacci ();
const firstNumbers = fibonacciSequence ()  
|> prendre (10)
|> toList;

//[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

Conclusion

L’opérateur de pipeline rend la transformation des données plus expressive.

Les générateurs fonctionnels peuvent être créés sur des séquences de valeurs finies ou infinies.

Avec les générateurs, nous pouvons traiter les listes sans créer de listes intermédiaires à chaque étape.

Vous pouvez vérifier tous les échantillons sur codesandbox. J'aimerais connaître votre opinion sur cette approche. Pour plus d'informations sur le côté fonctionnel de JavaScript, consultez:

Découvrez la programmation fonctionnelle en JavaScript avec cette introduction approfondie

Découvrez le pouvoir des fermetures en JavaScript

Comment la composition sans points fera de vous un meilleur programmeur fonctionnel

Comment améliorer votre code avec des noms de fonctions révélant l'intention

Voici quelques décorateurs de fonction que vous pouvez écrire à partir de zéro

Rendez votre code plus facile à lire avec la programmation fonctionnelle

Show More

SupportIvy

SupportIvy.com : Un lieu pour partager le savoir et mieux comprendre le monde. Meilleure plate-forme de support gratuit pour vous, Documentation &Tutoriels par les experts.

Related Articles

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Close
Close