Premiers pas vers la programmation fonctionnelle

Johnathan MEUNIER
Just-Tech-IT
Published in
5 min readSep 7, 2020

On entend souvent parler de programmation fonctionnelle et très rapidement on peut se perdre avec des termes ou des concepts relativement complexes : lambda, monade, currying, partial, effets de bord, immutabilité, etc.

Dans cet article, on va insister sur un concept simple et facile à mettre en place qui apporte immédiatement une plus-value à votre code : les fonctions pures. Pour ce faire, on abordera certains termes comme effet de bord, immutabilité et on aborde le concept de currying.

Photo by Alphacolor on Unsplash

Les exemples suivant sont en Javascript mais peuvent être appliqués dans tous les langages.

Qu’est ce qu’une fonction pure ?

Une fonction est pure quand elle répond au moins à ces deux conditions :

Aucun effet de bord

C’est à dire qu’elle ne doit pas altérer avec son environnement. Elle ne peut interagir que dans son scope, sans modifier des valeurs qui ne lui sont pas propres. Loguer un élément dans la console est également considéré comme un effet de bord. Voici différents exemples :

Déterministe

Le déterminisme est une notion philosophique selon laquelle la succession de chaque événement est déterminée en vertu du principe de causalité, du passé et des lois de la physique. — Wikipedia : Déterminisme

Dans notre cas, une fonction est dite déterministe si renvoie toujours le même résultat pour les mêmes paramètre d’entrée. Son comportement doit seulement dépendre de ses paramètres et d’aucune valeur extérieur.

Quel intérêt ?

Comme vous l’aurez deviné, l’un des principaux leitmotiv des fonctions pures (et de la programmation fonctionnelle) est d’avoir le moins de mutabilité possible. De façon macroscopique, on peut estimer que la mutabilité est la cause d’environ 50% des bugs sur un projet (modification de valeur non voulue, au mauvais moment, etc). Ici, on va chercher à encadrer la mutabilité au sein de petites méthodes fermées. L’idée est de renvoyer une nouvelle valeur sans altérer l’existant.

Une fonction pure sera également naturellement pragmatique. Elle cherchera à être la plus petite possible et à répondre à un seul but.

En conséquence, les tests d’une fonction pure sont très simples. Vérifier la valeur de sortie par rapport aux paramètres en entrée, être certain que la fonction appelle différentes méthodes, etc.

Petit à petit, on cherchera à composer différentes fonctions entre elles afin d’atteindre notre but. Je détaillerai pas la suite cette pratique grâce à différents exemples. Je vous laisse découvrir le Pipeline Operator qui est une proposition de la TC39 allant dans ce sens. Comme quoi, le monde du Javascript tend à évoluer dans ce sens !

Comment utiliser une fonction pure ?

Lorsque vous allez commencer à écrire des fonctions pures, vous devrez penser différemment votre code. Vous allez écrire des fonctions ayant un but très clair et répondant à une seule raison d’exister. Il faudra donc composer plusieurs fonctions pures pour développer une fonctionnalité.

Prenons un exemple simple. Je souhaite filtrer une tableau d’objets selon une chaine de caractère.

J’ai une liste de consoles, possédant chacun leur nom, leur constructeur, et leur année de sortie.

Mon application possède un champ de recherche qui va chercher dans tous les champs. Voici des exemples de ce que l’on souhaite récupérer en valeur finale:

  • 2017 => “Nintendo: Switch”
  • “Sony” => “Sony: Playstation 5” et “Sony: Playstation 4”
  • “Xbox” => “Microsoft: Xbox Series X” et “ Microsoft: Xbox One”

Comme vous pouvez le voir, cette demande est très facile à tester. Il suffit d’exécuter plusieurs fois des tests sur la méthode finale, en changeant les paramètres d’entrée et en testant simplement la sortie.

Composer des fonctions pures

L’idée est donc de faire des fonctions les plus pragmatiques possibles :

Dans un premier temps on va normaliser le champ de recherche grâce à une fonction normalize.

Puis faire notre recherche getConsoles :

Et enfin, formatter notre résultat grâce à getLabels :

En résultat, nous aurons :

En étant bien attentif, on se rend compte que getConsoles n’est pas tout à faire pure. En effet, elle utilise la fonction normalize, déclarée en dehors de son scope. Cette fonction pourrait être modifiée à tout moment et ne plus renvoyer la même chose.

Tout prendre en paramètres

C’est pourquoi, la solution serait de tout prendre en paramètres. getConsoles deviendrait donc :

Et la récupération des résultats se ferait en injectant la méthode normalize lors de l’appel à getConsoles :

Cette solution n’est pas parfaite, on peut très vite se retrouver avec énormément de paramètres à passer lors de l’appel de nos méthodes.

Currying

Et c’est ici qu’intervient le currying.

There is a way to reduce functions of more than one argument to functions of one argument, a way called currying- Handbook of the History of Logic — Volume 5

Le but est de réduire le nombre de paramètres que prennent nos méthodes (dans l’idéal à un seul paramètre) en créant des méthodes intermédiaires.

Un exemple très simple avec une méthode d’addition consiste à passer par une méthode intermédiaire qui ne prendra qu’un seul paramètre (curryedAdd) et renverra l’appel à une méthode qui attend elle aussi un seul paramètre puis une deuxième méthode qui appellera notre fonction curryed avec une valeur fixe déjà passée (addSeven) :

Et voici une autre manière d’utiliser la méthode du currying avec nos précédents exemple, la méthode getConsolesWithNormalize :

Conclusion

Les fonctions pures sont un premier pas vers la programmation fonctionnelle. Naturellement, vous écrirez des fonctions plus petites et précises, sans mutabilité. Bien évidemment, vous ne pouvez pas avoir que des fonctions pures dans votre application. Vous devrez dans beaucoup de cas faire appel à des webservices externes, interagir avec du localStorage ou des cookies, ou encore récupérer des valeurs en base de donnée.

Comme je l’expliquais en introduction, ces concepts peuvent être appliqués partout. Avec React par exemple, il est possible de faire des composants purs qui exposent seulement la vue en prenant tout en paramètres et un composant enrichi qui renvoie notre composant pur en lui ayant passé les propriétés nécessaires à son fonctionnement.

Pour aller plus loin, voici quelques ressources utiles :

--

--