La programmation purement fonctionnelle
Tags: programmation, javascript, programmationfonctionnelle
Published: 2023-10-10 19:33:36

Bonjour à vous. Aujourd'hui, on s'intéresse à la programmation purement fonctionnelle du point de vue d'un développeur utilisant la programmation impérative, en JavaScript.

## Introduction :

La programmation purement fonctionnelle est un paradigme de programmation déclarative dans lequel un programme est construit autour d'appels de fonctions et de compositions de fonctions. Il n'y a pas d'exécution séquentielle d'instruction, mais plutôt des appels consécutifs à des fonctions, elles-mêmes appelant d'autres fonctions, etc...

Dans ce paradigme, les fonctions peuvent être passées en paramètres, mais également devenir une valeur de retour, comme un type de donnée.

Le but étant d'avoir de toutes petites fonctions avec une seule tâche spécifique qui s'imbriquent avec d'autres fonctions, permettant au fur et à mesure de complexifier un programme.

Nous allons observer la programmation purement fonctionnelle avec ces quelques règles :
- fonction d'une ligne, constituée uniquement d'un `return`,
- pas plus d'un paramètre par fonction,
- pas de boucle,
- pas de condition (sauf opérateurs ternaires),
- pas d'effet indésirable :
    -   pas de modification de valeur des paramètres,
    -   pas d'Input/Output,
    -   pas de variables.
- pas de tableau,

## Tant de règles ?

Ça ne vous a probablement pas échappé, mais comment faire un programme qui tienne la route avec autant de restrictions ?

##### Fonction d'une ligne ? Pas plus d'un paramètre ?

Pour passer plusieurs paramètres à une fonction tout en respectant notre règle, il suffit de créer une fonction qui prend un paramètre et retourne une autre fonction, elle aussi prenant un paramètre.

Il s'agit d'une technique appelée `Currying`. Vous renvoyez autant de fonctions que de paramètres souhaités, jusqu'à pouvoir renvoyer un résultat.

Par exemple, voici une fonction qui effectue une somme entre deux nombres :

```javascript
// Programmation impérative :
const add = (a, b) => a + b;

// Programmation purement fonctionnelle :
const functionnalAdd = (a) => (b) => a + b;
```

##### Pas de boucle ? Pas de condition ?

Et non, vous n'avez pas besoin de boucle pour faire un programme. Si vous avez déjà programmé avant, ou même si vous avez suivi des cours de mathématiques, vous avez entendu parler respectivement de récursivité et de récurrence.

La récursivité consiste en une fonction qui s'appelle elle-même un nombre déterminé de fois, créant ainsi une boucle. Dans des langages comme Python, la récursivité connait sa limite à 1000 appels successifs. Dans d'autres langages, comme Haskell, cette limite n'existe pas et vous devrez vous-même interrompre un programme qui ne s'arrête pas.

Dans cet exemple de récursivité, nous allons coder une fonction appelée `sumtorial` qui prend un paramètre `n` correspondant à une limite telle que : `sumtorial(n) = 0 + 1 + 2 + ... + n - 2 + n - 1 + n`

```javascript
// Programmation impérative :
const sumtorial = (n) => {
    let result = 0;
    for (let i = 0; i <= n; ++i)
        result += i;
    return result;
}

// Programmation purement fonctionnelle :
const functionnalSumtorial = (n) => n === 0 ? 0 : n + functionnalSumtorial(n - 1);
```

##### Pas de tableau ?

Et oui, les tableaux ne sont pas admis ici. Nous allons à la place utiliser un système de paires. Une paire est similaire à une liste chaînée où les champs `data` et `next` correspondent respectivement aux champs `head` et `tail`. Vous pouvez la visualiser comme suit :

```json
{
    head: 30,
    tail: {
        head: 20,
        tail: {
            head: 10,
            tail: null
        }
    }
}
```

Pour l'implémentation, nous aurons besoin de trois fonctions.

Une première fonction qui construit la paire :

```javascript
const pair = (head) => (tail) => { return { head, tail }; }
```

Une deuxième fonction qui isole le champs `head` pour obtenir la valeur :

```javascript
const head = (pair) => pair.head;
```

Et une troisième fonction qui isole le champs `tail` pour obtenir le reste de la paire :

```javascript
const tail = (pair) => pair.tail;
```

Nous allons ensuite créer deux fonctions qui ne sont pas purement fonctionnelles. Il s'agit de deux fonctions qui transgressent nos règles uniquement à des fins de vérification. Elles sont à proscrire dans nos appels de fonctions.

```javascript
// Converti un tableau en une liste chaînée "paire".
const array2list = (array) => {
    let p = null;
    for (let i = 0; i < array.length; ++i)
        p = pair(array[i])(p);
    return p;
}

// Converti une liste chaînée "paire" en un tableau
const list2array = (p) => {
    const array = [];
    while (p !== null) {
        array.push(head(p));
        p = tail(p);
    }
    array.reverse();
    return array;
}
```

Pour vérifier qu'une paire est créée correctement, vous pouvez exécuter ce code :

```javascript
const paire = array2list([10, 20, 30]);
const liste = list2array(paire);

console.log(liste);

// Sortie :
// [ 10, 20, 30 ]
```

##### Implémentation de la fonction `range`

La fonction `range` prend deux paramètres. Un minimum et un maximum. Nous allons renvoyer une paire contenant tous les nombres entre le minimum inclu et le maximum exclu.

```javascript
const range = (min) => (max)  => max <= min ? null : pair(max - 1)(range(min)(max - 1));
```

Si le max est supérieur ou égal au min, on veut arrêter la récursivité car on a terminé l'iteration. Pour se faire, on renvoie `null`.

Sinon, on renvoie une paire avec pour `head`, `max - 1` (puisqu'on exclu le maximum), et comme `tail`, un appel récursif avec le même minimum, mais on soustrait `1` à `max` pour diminuer la prochaine valeur de la paire.

Ainsi, le code :

```javascript
console.log(range(0)(3));
```

Affichera cette paire :

```json
{
    head: 2,
    tail: {
        head: 1,
        tail: {
            head: 0,
            tail: null
        }
    }
}
```

##### Implémentation de la fonction `map`

La fonction `map` prend également deux paramètres. Le premier est une fonction, le deuxième est une paire.

Pour chaque élément de la paire, on appelle la fonction `f` avec comme paramètre, la `head` de la paire.

`map` nous renverra une nouvelle paire avec les nouvelles valeurs.

```javascript
const map = (f) => (p) => p === null ? null : pair(f(head(p)))(map(f)(tail(p)));
```

Ainsi, le code :

```javascript
console.log(list2array(map((x) => x + x)(range(0)(3))));
```

Produira la sortie suivante :
```json
{
    head: 4,
    tail: {
        head: 2,
        tail: {
            head: 0,
            tail: null
        }
    }
}
```

## Et maintenant ?

Maintenant, le plus dur est derrière nous. Nous avons une petite boîte à outils remplie de fonctions purement fonctionnelles, et on compte bien s'en servir !

Nous allons résoudre le célèbre problème `FizzBuzz`. Voyez le problème suivant :
- passez un nombre divisible par 3 à la fonction `Fizzbuzz`, vous obtiendrez `Fizz`,
- passez un nombre divisible par 5 à la fonction `Fizzbuzz`, vous obtiendrez `Buzz`,
- passez un nombre divisible par 3 et 5 à la fonction `Fizzbuzz`, vous obtiendrez `FizzBuzz`,
- passez un nombre qui n'est ni divisible par 3 ni par 5 à la fonction `Fizzbuzz`, vous obtiendrez ce même nombre.

Nous allons passez 100 nombres à la fonction `FizzBuzz`, allant de 1 à 100 inclu.

##### D'abord, `FizzBuzz`

Avant d'appliquer `FizzBuzz` sur une liste de 100 éléments, il faut déjà pouvoir l'appliquer sur un seul.

Voici une définition très épurée :

```javascript
const fizzbuzz = (n) => ((n % 3 === 0 ? "Fizz": "") + (n % 5 === 0 ? "Buzz": "")) || n;
```

Si `n % 3 === 0` alors `n` est divisible par 3 et on renvoie `Fizz`. On renvoie une chaîne vide sinon.

Si `n % 5 === 0` alors `n` est divisible par 5 et on renvoie `Buzz`. On renvoie une chaîne vide sinon.

Ensuite, on concatène les deux chaînes renvoyées par chacune des expressions. Les résultats possibles sont `Fizz`, `Buzz`, `FizzBuzz` ou une chaîne vide.

Enfin, on utilise l'opérateur `||` pour détecter une chaîne vide. Si la chaîne est vide, le membre de droite sera alors la valeur de retour. En l'occurence, notre nombre.

Nous avons respecté les règles du FizzBuzz mais également de la programmation purement fonctionnelle.

##### `FizzBuzz` Nous voilà !

Maintenant, nous pouvons appliquer `FizzBuzz` sur une liste de 100 nombres très facilement. Comment ? Et bien on utilise la fonction `range` pour avoir une liste de 1 à 100 inclu, on applique la fonction `fizzbuzz` à chaque élément de la liste grâce à la fonction `map`, et on affiche le résultat :

```javascript
const solution = map(fizzbuzz)(range(1)(101));

console.log(list2array(solution));
```

Voilà l'affichage que vous devriez obtenir :

```json
[
  1,      2,      'Fizz',     4,      'Buzz', 'Fizz',
  7,      8,      'Fizz',     'Buzz', 11,     'Fizz',
  13,     14,     'FizzBuzz', 16,     17,     'Fizz',
  19,     'Buzz', 'Fizz',     22,     23,     'Fizz',
  'Buzz', 26,     'Fizz',     28,     29,     'FizzBuzz',
  31,     32,     'Fizz',     34,     'Buzz', 'Fizz',
  37,     38,     'Fizz',     'Buzz', 41,     'Fizz',
  43,     44,     'FizzBuzz', 46,     47,     'Fizz',
  49,     'Buzz', 'Fizz',     52,     53,     'Fizz',
  'Buzz', 56,     'Fizz',     58,     59,     'FizzBuzz',
  61,     62,     'Fizz',     64,     'Buzz', 'Fizz',
  67,     68,     'Fizz',     'Buzz', 71,     'Fizz',
  73,     74,     'FizzBuzz', 76,     77,     'Fizz',
  79,     'Buzz', 'Fizz',     82,     83,     'Fizz',
  'Buzz', 86,     'Fizz',     88,     89,     'FizzBuzz',
  91,     92,     'Fizz',     94,     'Buzz', 'Fizz',
  97,     98,     'Fizz',     'Buzz'
]
```

## Conclusion :

La programmation fonctionnelle est un paradigme fascinant parce qu'il permet d'avoir des fonctions qui n'ont qu'une seule tâche, ce qui les rend simple à débogguer. Ce paradigme épure le code que vous auriez l'habitude de produire.

En dehors de ça, étant relativement peu exploité, il force le développeur à explorer de nouvelles façons de penser et de réfléchir à son code.

Si vous aviez peur de la programmation purement fonctionnelle, j'espère que vous aurez maintenant envie d'en apprendre plus.

Je remercie le streamer / YouTuber `tsoding` qui ma transmit ce savoir. Il s'agit d'un russe anglophone que je suis depuis un moment et que je trouve très intelligent / pédagogue. Si vous parlez anglais, c'est une mine d'or d'informations. Voici sa chaîne YouTube :

[https://www.youtube.com/tsoding](https://www.youtube.com/tsoding)

Voici le code complet :

```javascript
/**
 * Programmation purement fonctionnelle 
 */

const pair = (head) => (tail) => { return { head, tail }; }
const head = (pair) => pair.head;
const tail = (pair) => pair.tail;
const range = (min) => (max)  => max <= min ? null : pair(max - 1)(range(min)(max - 1));
const map = (f) => (p) => p === null ? null : pair(f(head(p)))(map(f)(tail(p)));
const fizzbuzz = (n) => ((n % 3 === 0 ? "Fizz": "") + (n % 5 === 0 ? "Buzz": "")) || n;
const solution = map(fizzbuzz)(range(1)(101));

/**
 * Programmation non purement fonctionnelle
 * Les règles peuvent être transgresser ici uniquement.
 */

const array2list = (array) => {
    let p = null;
    for (let i = 0; i < array.length; ++i)
        p = pair(array[i])(p);
    return p;
}

const list2array = (p) => {
    const array = [];
    while (p !== null) {
        array.push(head(p));
        p = tail(p);
    }
    array.reverse();
    return array;
}

console.log(list2array(solution));
```