Programmation fonctionnelle en TypeScript : de la théorie à la pratique
La programmation fonctionnelle (PF) est un paradigme dans lequel les calculs sont traités comme l'évaluation de fonctions mathématiques, en évitant les changements d'état et les données mutables. TypeScript, étant un sur-ensemble de JavaScript, est parfaitement adapté à l'application des principes de la PF grâce à son typage strict et à sa prise en charge des fonctions d'ordre supérieur. Dans cet article, nous allons analyser les concepts clés de la PF en TypeScript et montrer comment les appliquer en pratique.
1. Fonctions pures et immutabilité
Fonction pure — c'est une fonction qui :
- Retourne toujours le même résultat pour les mêmes arguments (déterminisme).
- N'a pas d'effets secondaires (ne modifie pas les variables externes, n'affiche pas de données, n'effectue pas de requêtes).
Exemple de fonction pure :
// Fonction pure : additionne deux nombresconst add = (a: number, b: number): number => a + b;
// Retournera toujours 5console.log(add(2, 3)); // 5console.log(add(2, 3)); // 5Immutabilité — principe selon lequel les données ne sont pas modifiées après leur création. Au lieu de modifier un objet existant, on en crée un nouveau.
// Mise à jour immuable d'un tableauconst numbers: number[] = [1, 2, 3];
// Mauvais (mutation) :numbers.push(4);
// Bon (immuable) :const newNumbers = [...numbers, 4]; // [1,2,3,4]
// Mise à jour immuable d'un objetinterface User { name: string; age: number;}
const user: User = { name: 'Alice', age: 30 };const updatedUser: User = { ...user, age: 31 }; // nouvel objet2. Composition de fonctions et curryfication
Composition de fonctions — c'est la combinaison de deux ou plusieurs fonctions en une seule nouvelle. Le résultat de la première fonction est passé comme argument à la seconde.
// Composition de deux fonctionsconst compose = <T, U, V>(f: (x: U) => V, g: (x: T) => U) => (x: T): V => f(g(x));
const double = (x: number): number => x * 2;const increment = (x: number): number => x + 1;
const doubleThenIncrement = compose(increment, double);console.log(doubleThenIncrement(3)); // (3*2)+1 = 7Curryfication — transformation d'une fonction avec plusieurs arguments en une séquence de fonctions avec un seul argument.
// Fonction normaleconst multiply = (a: number, b: number): number => a * b;
// Version curryfiéeconst curriedMultiply = (a: number) => (b: number): number => a * b;
const multiplyBy2 = curriedMultiply(2);console.log(multiplyBy2(5)); // 10console.log(multiplyBy2(7)); // 14La curryfication simplifie l'application partielle et la réutilisation des fonctions.
3. Foncteurs et monades : Maybe et Either
Foncteur — c'est un conteneur qui implémente la méthode map, permettant d'appliquer une fonction à la valeur à l'intérieur du conteneur. En TypeScript, un tableau est un foncteur, par exemple :
const numbers = [1, 2, 3];const doubled = numbers.map(x => x * 2); // [2, 4, 6]Monade — c'est un foncteur qui implémente en plus la méthode flatMap (ou bind) et of (ou return). Les monades aident à gérer les effets secondaires, les valeurs null et les erreurs sans exceptions.
Implémentons une monade simple Maybe pour travailler en toute sécurité avec null/undefined :
// Monade Maybeclass Maybe<T> { private constructor(private value: T | null) {}
static of<T>(value: T): Maybe<T> { return new Maybe(value); }
static none<T>(): Maybe<T> { return new Maybe<T>(null); }
map<U>(fn: (value: T) => U): Maybe<U> { if (this.value === null || this.value === undefined) { return Maybe.none(); } return M