<
Media
>
Article

La programmation fonctionnelle en JavaScript

7 min
04
/
02
/
2025

Introduction

La programmation fonctionnelle attire de plus en plus de développeurs grâce à ses concepts et avantages. De nombreuses librairies JavaScript adoptent ce paradigme pour améliorer la modularité et la réutilisabilité du code.

Dans cet article, nous allons expliquer ce qu’est la programmation fonctionnelle avec une approche mathématique (pas de panique ! Ce sont des maths basiques), ses bases et comment l'utiliser efficacement en JavaScript.

Nous aborderons ses avantages et inconvénients, quelques exemples pratiques, puis nous verrons la librairie Ramda qui fournit un ensemble de fonctionnalités utiles pour la programmation fonctionnelle.

1. Programmation fonctionnelle : objectifs et concepts fondamentaux

La programmation fonctionnelle est un paradigme de programmation qui se différencie de la programmation classique par l'utilisation de concepts clés tels que les fonctions pures, l'immutabilité des données et la composition de fonctions. Ces principes permettent d'écrire du code modulaire, prévisible et facile à tester.

1.1 Pourquoi la programmation fonctionnelle ?

L'objectif de la programmation fonctionnelle est de rendre le code prévisible, facile à comprendre (quand on sait le lire) et maintenable facilement en réduisant ses effets de bords au maximum.

Ce paradigme devient particulièrement pertinent dans le contexte du développement d'applications modernes qui manipulent de grandes quantités de données.

Combinée à TypeScript et à des tests unitaires, la programmation fonctionnelle améliore considérablement la robustesse du code.  Chaque fonction est testée de manière isolée, et les types assurent une cohérence tout au long du développement. Cela permet de détecter les erreurs plus tôt et de garantir une meilleure stabilité de l'application.

1.2 Les concepts fondamentaux

1.2.1 Les fonctions pures

Une fonction dite 'pure' ne dépend que de ses arguments et renvoie systématiquement le même résultat pour des entrées identiques, tout comme en mathématiques où 1 + 1 est toujours égal à 2.

// Fonction pure
// a + b
function add(a, b) {
    return a + b;
}

add(2, 3); // -> 5
add(2, 3); // -> Toujours 5

// Fonction non-pure
let total = 0;

function addToTotal(value) {
    total += value;
    return total;
}

addToTotal(3); // 3
addToTotal(3); // 6 | le résultat change
addToTotal(3); // 9 | parce qu'il y a du side-effect

1.2.2 L'immutabilité

L'immutabilité est un concept clé de la programmation fonctionnelle, garantissant que les données ne peuvent pas être modifiées après leur création.

// Approche classique (mutable)
const tableau = [1, 2, 3];
tableau.push(4);
tableau                  // [1, 2, 3, 4]

// Approche fonctionnelle (immutable)
const tableauImmuable = [1, 2, 3];
const nouveauTableau = tableauImmuable.concat([4]); // Renvoie un nouveau tableau
// OU
const nouveauTableau2 = [...tableauImmuable, 4];

tableauImmuable          // [1, 2, 3]
nouveauTableau           // [1, 2, 3, 4]
nouveauTableau2          // [1, 2, 3, 4]

Dans l'approche mutable, le tableau original est modifié en ajoutant un élément, tandis que dans l'approche immutable, des nouveaux tableaux sont créés et basés sur l'original sans l'altérer.

A noter : en JS, les tableaux et les objets sont mutables par défaut, permettant des modifications après leur création, même lorsqu'ils sont déclarés comme const. Pour garantir leurs immutabilités, on peut utiliser Object.freeze(), empêchant toute modification. Pour les tableaux, des méthodes comme map(), filter(), et concat() créent de nouvelles instances sans altérer l'original.

1.2.3 La composition de fonctions

La composition sert à combiner des fonctions simples pour créer des opérations plus complexes.

Imaginons deux fonctions pures :

const double = x => x * 2;
const square = x => x * x;

Maintenant, créons notre fonction de composition

// sous forme de fonction anonyme
const compose = (a, b) => x => a(b(x));
// ou sous forme de fonction nommée
function compose(a, b) {
    return function(x) {
        return a(b(x));
    }
}

Voici ce qu'il fait étape par étape :

  1. <span class="css-span">compose</span> prend 2 fonctions en paramètres : <span class="css-span">a</span> et <span class="css-span">b</span>
  2. Elle retourne une nouvelle fonction
  3. Cette fonction prend un argument <span class="css-span">x</span>
  4. Elle applique d'abord <span class="css-span">b</span> sur <span class="css-span">x</span> : <span class="css-span">b(x)</span>
  5. Puis elle applique <span class="css-span">a</span> sur le résultat de <span class="css-span">b(x)</span> : <span class="css-span">a(b(x))</span>

Pour y voir plus clair, essayons cet exemple d'implémentation : <span class="css-span">compose(square, double)</span>;

const doubleThenSquare = compose(square, double);
doubleThenSquare(3); // 36

Donc <span class="css-span">doubleThenSquare</span> est une fonction qui applique séquentiellement <span class="css-span">double</span> puis <span class="css-span">square</span> :

  1. <span class="css-span">doubleThenSquare(3)</span>
  2. <span class="css-span">double(3)</span> => (3 * 2) -> 6
  3. <span class="css-span">square(6)</span> => (6 * 6) -> 36
  4. Résultat : 36

Explication en image :

Pour résumer, avec <span class="css-span">compose(f, g)</span>, la fonction <span class="css-span">g</span> est exécutée en premier sur l'entrée, puis son résultat est passé à la fonction <span class="css-span">f</span>. Par exemple, dans <span class="css-span">compose(square, double)</span>, on exécute d'abord <span class="css-span">double(3)</span> qui donne 6, puis <span class="css-span">square(6)</span> qui donne <span class="css-span">36</span>.

Il existe une variante nommée <span class="css-span">pipe</span>, qui fait exactement la même chose que <span class="css-span">compose</span>, mais inverse l'ordre d'exécution des fonctions. Avec <span class="css-span">pipe(f, g)</span>, la fonction <span class="css-span">f</span> est exécutée en premier, puis son résultat est passé à <span class="css-span">g</span>. Par exemple, dans <span class="css-span">pipe(square, double)</span>, on exécute d'abord <span class="css-span">square(3)</span> qui donne 9, puis <span class="css-span">double(9)</span> qui donne <span class="css-span">18</span>.

Puissant non ?

1.2.4 La curryfication

Rien à voir avec une épice indienne, la curryfication est une technique qui transforme une fonction à plusieurs arguments en une séquence de fonctions à un seul argument.

Cette technique permet de créer des fonctions plus flexibles et réutilisables.

Exemple :

// Fonction non curryfiée
function add(a, b) {
  return a + b;
}

// Fonction curryfiée
function curriedAdd(a) {
  return function(b) {
    return a + b;
  }
}

// Utilisation
add(2, 3);                     // 5
const addTwo = curriedAdd(2);  // Retourne une fonction
addTwo(3);                     // 5

add(5, 10);                    // 5
const addFive = curriedAdd(5); // Retourne une fonction
addFive(10);                   // 15

Explication en image :

2. Quels sont les avantages et inconvénients à l'utilisation de la programmation fonctionnelle ?

2.1 Avantages

  • Code plus lisible : En utilisant des fonctions pures et une approche déclarative, le code devient plus simple à comprendre.
  • Facilité de test : Les fonctions pures étant prévisibles, les tester devient plus direct et efficace.
  • Réduction des effets de bord : L'immutabilité minimise les changements inattendus, ce qui rend le code plus stable.
  • Réutilisabilité accrue : Grâce à la composition de fonctions, il est facile de réutiliser des blocs de code.

2.2 Inconvénients

  • Courbe d'apprentissage : Adopter une nouvelle façon de penser peut être difficile pour des développeurs habitués à l'impératif.
  • Performance : L'immutabilité et la création de nouvelles structures de données peuvent affecter les performances.
  • Verbose : Certains concepts comme la curryfication ou la composition peuvent rendre le code plus verbeux.
  • Implémentation manuelle : L'implémentation de certains concepts comme la curryfication ou la composition peut être fastidieuse, heureusement des librairies comme Ramda existent pour nous faciliter la tâche.

3. La librairie Ramda

Pas besoin de réinventer la roue pour curryfier ou composer vos fonctions. Avec la librairie Ramda, tout est déjà à portée de main. Cette bibliothèque offre des fonctionnalités clés en main pour appliquer facilement des concepts de programmation fonctionnelle en JavaScript.

3.1 Pourquoi Ramda ?

Ramda est une bibliothèque JavaScript populaire conçue pour faciliter la programmation fonctionnelle. Alors que JavaScript offre déjà certaines méthodes fonctionnelles comme map(), filter(), et reduce(), Ramda va beaucoup plus loin en fournissant un ensemble d'outils qui s'adaptent parfaitement aux principes de la programmation fonctionnelle : immutabilité, fonctions pures, composition, et curryfication.

3.2 Le Curry dans Ramda

Chaque fonction fournie par Ramda est curryfiée par défaut, ce qui signifie que vous pouvez facilement créer des fonctions partielles et réutilisables sans avoir à les curryfier vous-même. De plus, avec la fonction curry vous avez la possibilité de curryfier vos propres fonctions :

import { add, curry } from 'ramda';

const addFive = add(5);
addFive(10); // 15

const addThreeNumbers = curry((a, b, c) => a + b + c);

const addTwoAndFive = addThreeNumbers(2, 5);
addTwoAndFive(3); // 10

3.3 Compose & Pipe

Ramda offre également les fonctions compose() et pipe() pour composer vos fonctions de manière simple et élégante. La différence entre compose et pipe est toujours l'ordre dans lequel les fonctions sont appliquées :

  • compose() applique les fonctions de droite à gauche.
  • pipe() applique les fonctions de gauche à droite.

Voyons cela avec notre exemple de composition de fonctions double et square :

import { compose, pipe } from 'ramda';

const double = x => x * 2;
const square = x => x * x;

const doubleThenSquare = compose(square, double);
doubleThenSquare(3); // 36

const squareThenDouble = pipe(square, double);
squareThenDouble(3); // 18

3.4 Picking et manipulations des données

Les fonctions prop et path permettent d'accéder aux propriétés d'objets de manière sécurisée et fonctionnelle.

  • prop récupère la valeur d'une propriété dans un objet, pratique pour extraire des données d'un seul niveau.
  • path accède aux propriétés imbriquées sur plusieurs niveaux et retourne <span class="css-span">undefined</span> si le chemin n'existe pas.

Ces fonctions sont particulièrement utiles pour travailler avec des structures de données complexes, tout en garantissant que votre code reste sûr et fonctionnel.

import { prop, path, applySpec } from 'ramda';

const user = {
  name: 'Alice',
  address: {
    city: 'Rennes',
    zip: '35000'
  }
};

const getUserName = prop('name');
const getUserCity = path(['address', 'city']);

const userName = getUserName(user);// "Alice"
const userCity = getUserCity(user);  // "Rennes"

Parlons rapidement de <span class="css-span">applySpec</span>.

Cette fonction prend en entrée un objet où chaque clé correspond à une fonction. Elle renvoie ensuite une nouvelle fonction qui, lorsqu'elle est appelée, applique ces fonctions aux arguments fournis. Cela permet de structurer les données en leur appliquant différentes transformations.

import { applySpec, multiply } from 'ramda';

const square = x => x * x;

const calculates = applySpec({
  double: multiply(2),
  square: square,
});

calculates(8); // {"double": 16, "square": 64}

const getUserName = prop('name');
const getUserCity = path(['address', 'city']);

const getUserInfo = applySpec({
  userName: getUserName,
  userCity: getUserCity,
});

const user = {
  name: 'Alice',
  address: {
    city: 'Rennes',
    zip: '35000'
  }
};

getUserInfo(user); // {"userCity": "Rennes", "userName": "Alice"}

Bien évidemment je ne fais que survoler quelques fonctionnalités, mais la bibliothèque offre une large gammes d’outils puissants.

Allez jeter un oeil à la documentation de Ramda

4. Conclusion

La programmation fonctionnelle offre un cadre puissant pour écrire du code JavaScript plus modulaire, prévisible et réutilisable, favorisant une meilleure productivité. Ce paradigme permet de structurer votre code en petites unités, indépendantes, simples et facilement lisibles, efficaces et maintenables, faciles à tester, et à combiner. Avec la librairie Ramda, cette approche devient encore plus accessible et pratique à mettre en oeuvre.

Bien que la courbe d'apprentissage puisse être difficile au début, une fois maîtrisée, elle transforme profondément la qualité, la stabilité et la flexibilité de votre code. Ajoutant une belle corde à votre arc et enrichissant votre bagage technique.

Il est peut-être temps d'essayer ?

No items found.
ça t’a plu ?
Partage ce contenu
Benjamin

Passionné par les étoiles, la programmation et les jeux-vidéos, Benjamin est un spécialiste en JavaScript, avec une spécialisation en ReactJS. Il est constamment à la recherche de nouvelles technologies pour élargir ses compétences, considérant chaque innovation comme une opportunité pour explorer de nouvelles perspectives.