La programmation fonctionnelle est un paradigme de construction de programmes informatiques utilisant des expressions et des fonctions sans modifier l'état et les données.
En respectant ces restrictions, la programmation fonctionnelle vise à écrire du code plus facile à comprendre et moins sujet aux bogues. Cela est possible en évitant les instructions de contrôle de flux (for, while, break, continue, goto) qui rendent le code plus difficile à suivre. De plus, la programmation fonctionnelle nous oblige à écrire des fonctions pures et déterministes, moins susceptibles de contenir des bogues.
Dans cet article, nous parlerons de la programmation fonctionnelle en utilisant JavaScript. Nous explorerons également diverses méthodes et fonctionnalités de JavaScript qui la rendent possible. Enfin, nous examinerons différents concepts associés à la programmation fonctionnelle et verrons pourquoi ils sont si puissants.
Avant d'entrer dans la programmation fonctionnelle, il est nécessaire de comprendre la différence entre les fonctions pures et impures.
Fonctions pures vs. impures
Les fonctions pures prennent des entrées et donnent une sortie fixe. De plus, elles ne provoquent aucun effet secondaire sur le monde extérieur.
const add = (a, b) => a + b;
Ici, add est une fonction pure. En effet, pour une valeur fixe de a et b, la sortie sera toujours la même.
const SECRET = 42;
const getId = (a) => SECRET * a;
getId n'est pas une fonction pure. La raison est qu'elle utilise la variable globale SECRET pour calculer la sortie. Si SECRET change, la fonction getId renverra une valeur différente pour la même entrée. Ainsi, ce n'est pas une fonction pure.
let id_count = 0;
const getId = () => ++id_count;
Ceci est également une fonction impure pour deux raisons
- 1 elle utilise une variable non locale pour calculer sa sortie,
- 2 elle crée un effet secondaire en modifiant une variable dans le monde extérieur.
Cela peut poser problème si nous devons déboguer ce code.
Quelle est la valeur actuelle de id_count ? Quelles autres fonctions modifient id_count ? D'autres fonctions dépendent-elles de id_count ?
Pour ces raisons, nous n'utilisons que des fonctions pures en programmation fonctionnelle.
Un autre avantage des fonctions pures est qu'elles peuvent être parallélisées et mémorisées. Regardez les deux fonctions précédentes. Il est impossible de les paralléliser ou de les mémoriser. Cela aide à créer un code performant.
Les principes de la programmation fonctionnelle
Jusqu'à présent, nous avons appris que la programmation fonctionnelle dépend de quelques règles. Elles sont les suivantes :
- Ne pas muter les données
- Utiliser des fonctions pures : sortie fixe pour des entrées fixes et pas d'effets secondaires
- Utiliser des expressions et des déclarations
Lorsque nous respectons ces conditions, nous pouvons dire que notre code est fonctionnel.
Programmation fonctionnelle en JavaScript
JavaScript propose déjà des fonctions qui permettent la programmation fonctionnelle. Exemple : String.prototype.slice, Array.prototype.filter, Array.prototype.join.
D'autre part, Array.prototype.forEach et Array.prototype.push sont des fonctions impures.
On pourrait argumenter que Array.prototype.forEach n'est pas impure par conception, mais pensez-y — il est impossible de l'utiliser sans muter des données non locales ou créer des effets secondaires. Par conséquent, il est acceptable de la placer dans la catégorie des fonctions impures.
De plus, JavaScript possède la déclaration const, qui est parfaite pour la programmation fonctionnelle puisque nous ne mutons aucune donnée. Fonctions pures en JavaScript
Voyons quelques-unes des fonctions (méthodes) pures fournies par JavaScript.
Filter
Comme son nom l'indique, cette méthode filtre le tableau.
array.filter(condition);
La condition ici est une fonction qui reçoit chaque élément du tableau, et elle doit décider s'il faut garder l'élément ou non, et retourner une valeur booléenne.
const filterEven = x => x%2 === 0;
[1, 2, 3].filter(filterEven); // [2]
Remarquez que filterEven est une fonction pure. Si elle avait été impure, cela aurait rendu l'appel filter impure.
Map
map applique une fonction à chaque élément du tableau et crée un nouveau tableau basé sur les valeurs de retour.
array.map(mapper)
Le mapper est une fonction qui prend un élément du tableau en entrée et retourne une sortie.
const double = x => 2 * x;
[1, 2, 3].map(double); // [2, 4, 6]
Reduce
reduce réduit le tableau à une seule valeur.
array.reduce(reducer);
Le reducer est une fonction qui prend la valeur accumulée et l'élément suivant du tableau, et retourne la nouvelle valeur.
const sum = (accumulatedSum, arrayItem) => accumulatedSum + arrayItem;
[1, 2, 3].reduce(sum);
// 6
Concat
concat ajoute de nouveaux éléments à un tableau existant pour créer un nouveau tableau. Il est différent de push() en ce sens que push() mute les données, ce qui le rend impur.
[1, 2].concat([3, 4]) // [1, 2, 3, 4]
Cela peut également être fait en utilisant l'opérateur spread.
[1, 2, ...[3, 4]]
Object.assign
Object.assign copie les valeurs de l'objet fourni vers un nouvel objet. Puisque la programmation fonctionnelle est fondée sur des données immuables, nous l'utilisons pour créer de nouveaux objets basés sur des objets existants.
const obj = {a : 2};
const newObj = Object.assign({}, obj);
newObj.a = 3;
obj.a; // 2
Avec l'introduction d'ES6, cela peut également être fait en utilisant l'opérateur spread.
const newObj = {...obj};
Création de votre propre fonction pure
Nous pouvons également créer notre propre fonction pure. Faisons-en une pour dupliquer une chaîne n fois.
const duplicate = (str, n) => n < 1 ? '' : str + duplicate(str, n-1);
Cette fonction duplique une chaîne n fois et retourne une nouvelle chaîne.
duplicate('hourra!', 3); // hourra!hourra!hourra!
Fonctions d'ordre supérieur
Les fonctions d'ordre supérieur sont des fonctions qui acceptent une fonction en argument et retournent une fonction. Souvent, elles sont utilisées pour ajouter de la fonctionnalité à une fonction.
const withLog = (fn) => {
return (...args) => {
console.log(`appel de ${fn.name}`);
return fn(...args);
}
}
Dans cet exemple, nous créons une fonction d'ordre supérieur withLog qui prend une fonction et retourne une fonction qui enregistre un message avant que la fonction encapsulée ne s'exécute.
const add = (a, b) => a + b;
const addWithLogging = withLog(add);
addWithLogging(3, 4); // appel de add => 7
Currying
Le currying signifie décomposer une fonction qui prend plusieurs arguments en un ou plusieurs niveaux de fonctions d'ordre supérieur.
Prenons la fonction add.
const add = (a, b) => a + b;
Lorsqu'on la curryifie, nous la réécrivons en distribuant les arguments sur plusieurs niveaux comme suit.
const add = a => {
return b => {
return a + b;
}
}
Le bénéfice du currying est la mémoïsation. Nous pouvons maintenant mémoïser certains arguments dans un appel de fonction pour qu'ils puissent être réutilisés plus tard sans duplication ni recalcul.
Composition
En mathématiques, la composition est définie comme le fait de passer la sortie d'une fonction dans l'entrée d'une autre afin de créer une sortie combinée. Cela est possible en programmation fonctionnelle puisque nous utilisons des fonctions pures.
Prenons l'exemple de deux fonctions.
const range = (a, b) => a > b ? [] : [a, ...range(a+1, b)];
const multiply = arr => arr.reduce((p, a) => p * a);
const factorial = n => multiply(range(1, n));
factorial(5); // 120
Le mot de la fin en programmation fonctionnelle.
Nous avons parcouru les fonctions pures et impures, la programmation fonctionnelle, les nouvelles fonctionnalités de JavaScript qui la favorisent, ainsi que quelques concepts clés de la programmation fonctionnelle.
Nous espérons que cet article éveille votre intérêt pour la programmation fonctionnelle et vous incite à l'essayer dans votre code. C'est une expérience d'apprentissage enrichissante et une étape importante dans votre parcours de développement logiciel.
La programmation fonctionnelle est un paradigme bien étudié et robuste pour écrire des programmes informatiques. Avec l'introduction d'ES6, JavaScript permet une bien meilleure expérience de programmation fonctionnelle qu'auparavant.
Vous avez besoin d'un développeur compétent ? Faire appel à des personnes qui ont fait leurs preuves ? Contactez-nous.
En résumé
- La programmation fonctionnelle en JavaScript repose sur l'utilisation de fonctions pures, sans mutation de données, pour créer un code plus compréhensible et moins sujet aux bugs.
- JavaScript offre plusieurs méthodes pures comme filter, map, et reduce, ainsi que des fonctionnalités comme const et l'opérateur spread, qui facilitent la programmation fonctionnelle.
- Les concepts avancés tels que les fonctions d'ordre supérieur, le currying et la composition permettent d'écrire un code plus flexible et réutilisable en programmation fonctionnelle.