La programmation fonctionnelle est un paradigme de programmation (au même titre que la programmation procédurale ou objet). De la même manière que la programmation objet est une évolution de la programmation procédurale, la programmation fonctionnelle est une évolution de la programmation objet. Celle ci peut être utilisée en complément de ces deux paradigmes.
Tout au long de cet article nous utiliserons des exemples en javascript. Même si ce n'est pas un langage de programmation fonctionnelle (au même titre qu'Erlang, Haskell, Elm, Elixir ou encore Scala) il intègre la pluspart des concepts de programmation fonctionnelle.
Les concepts et avantages
La programmation fonctionnelle repose sur plusieurs principes :
Immutabilité
Ce concept permet de s'assurer de la valeur des variables du début à la fin de nos actions. Cela va permettre d'avoir un code plus robuste et plus stable, donc moins de bugs et moins de maintenances. Tout au long de notre script les variables ne peuvent être modifiées.
Le principe d'immutabilité permet d'éviter les surprises en évitant les modifications implicites des variables.
L'utilisation de l'immutabilité peut avoir un effet négatif sur les performances en multipliant les données à stocker en mémoire. Pour limiter ces problèmes les langages de programmation peuvent sauvgarder les structure complexes (tableau, map, hash) comme un ensemble de référence (une sorte d'arbre).
Le JavaScript est très peu adapté pour cette notion d'immutabilité mais des librairies comme ImmutableJS permet de combler ces besoins
Les fonctions pures
Une fonction pure est une fonction qui remplit les 2 conditions suivantes :
- Le résultat de la fonction ne dépend que des arguments et pas du contexte extérieur
- La fonction n'a pas d'effets de bords / d'effets secondaires
Les fonctiones pures permettent d'avoir un code qui est thread safe, c'est à dire que l'on peut déléguer l'éxécution d'une fonction à un process séparé sans avoir à envoyer tout le contexte de notre application. Elles permettent aussi d'avoir un code qui est plus facilement testable gràce à une isolation naturelle.
Les fonctions d'ordre supérieur
Les fonctions d'ordre supérieur sont des fonctions qui possèdent au moins une des propriétés suivantes :
Avoir une ou plusieurs fonctions en paramètre
Vous avez sûrement déjà utilisé ce concept sans connaitre son nom. Voici un exemple :
On peut voir que le code est compact et très simple à lire. Voici l'équivalent en impératif :
On se rend compte que le code est beaucoup moins lisible dans ce deuxième cas et qu'il va être obligatoire d'écrire des commentaires pour simplifier sa lecture.
Renvoyer une fonction
Cette propriété permet de renvoyer une fonction au lieu d'une valeur. Voici un exemple :
Les lambdas
Les lambdas, aussi appelées fonctions anonymes, sont des fonctions utilisées de manière ponctuelle et n'effectuant généralement qu'une opération. Reprenons l'exemple de isEven().
Certains langages, comme JavaScript ou PHP sont encore verbeux sur l'utilisation des lambdas. Java, Ruby ou Scala exploitent toute la puissance des lambdas pour avoir un code à la fois concis, clair et robuste.
Récursivité
Il est possible d'utiliser la programmation fonctionnelle de manière récursive.
"En informatique et en logique, une fonction ou plus généralement un algorithme qui contient un appel à elle-même est dite récursive." - Wikipedia
Cela va permettre d'avoir un code plus lisible et plus court. De plus, le code s'auto documenter de lui-même. Voici un exemple d'une fonction récursive.
Tail call
La récusrivité peut en revanche poser des problèmes dans certains cas. Par exemple
Le problème est que la profondeur d'éxécution est trop importante pour le langage qui comprend le code de la manière suivante.
Les langage de programmation fonctionnelle dispose d'un cas particulier appeller tail call ou récursion terminale où, si la dernière instruction d'une fonction est un appel à cette dernière, alors la récursion peut être vue comme une itération.
Il faut donc bien réfléchir à l'écriture de vos fonctions récursives
Map, Filter & Reduce
Map, Filter et Reduce sont des fonctions essentielles en programmation fonctionnelle qui permettent de remplacer la pluspart des problèmes que l'on résoud en programmation fonctionnelle avec des if ou des for.
map()
Cette méthode va permettre de transformer une liste. map() doit être utilisé de manière immutable (d'un objet A vers un objet B), c'est à dire que vous ne devez pas introduire d'effet de bord.
filter()
C'est une fonction que vous avez souvent vu dans cet article. Comme son nom l'indique, il prend en paramètre deux éléments :
- un tableau
- une fonction permettant de tester si la valeur correspond à notre filtre et doit être conservée.
reduce
Cette méthode est un poil plus complexe et permet de transformer une liste au travers d'un accumulateur
Performances
Par contre, enchainer ces méthodes peut avoir un impact négatif sur les performances car le tableau est parcouru à plusieurs reprises.
Ici le map et le reduce oblige le parcours du tableau en double. Certains langages comme Elixir propose un système de Stream pour améliorer les performances :
Le stream permet de garder en mémoire les opération à effectuer pour transformer la liste, mais n'effectuera les opérations nécessaire qu'à la transformation.
La programmation fonctionnelle par langage
JavaScript
JavaScript utilise certains concept de programmation fonctionnelle depuis quelques temps déjà. Lorsque vous écoutez un évènement par exemple vous utilisez déjà les lambdas sans le savoir. En revanche c'est un langage qui reste basé en grande partie sur des mutations. Certains framework comme Redux impose une approche immutable qui est simplifié par la syntaxe ES6. Mais il est possible de pousser encore plus loin avec des librairies comme ImmutableJs
PHP
PHP possède aussi des notions de programmation fonctionnelle, cependant celles-ci sont moins bien intégrées dans le langage car beaucoup plus récentes. Il aura fallu attendre la version 5.3 de PHP pour enfin voir les fonctions anonymes apparaitre.
Ensuite, le chainage de fonctions rend la compréhension moins facile, alors que cela devrait être le contraire.
On peut voir qu'en PHP, outre l'inconsistance de l'ordre des paramètres, il est nécessaire de lire de droite à gauche pour comprendre le mécanisme. La lecture est moins fluide, mais le résultat sera similaire.
Java
Java est un langage avec une orientation objet très forte. A ce titre, beaucoup de développeurs étaient réticents à l'import de la programmation fonctionnelle dans ce langage. Il apparait néanmoins timidement dans la version 8.
Android n'intégrant pas encore la version 8 de Java, la programmation fonctionnelle n'est pas encore prise en charge de manière officielle.
Scala
Scala répond au besoin des développeurs Java souhaitant avoir un langage fonctionnel. En ce sens, l'intégration de la programmation fonctionnelle est très poussée.
Swift
Swift est le nouveau langage de programmation d'Apple. Sa syntaxe étant plus légère qu'Objective-C, il intègre les composantes de programmation fonctionnelle contrairement à son ainé.
Ruby
Ruby intègre dans son langage les concepts de programmation fonctionnelle avec l'utilisation des Block, Proc et Lamba.
En revanche cela reste un langage en grande partie basé sur des mutations.