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.