Introduction à la programmation fonctionnelle

On distingue deux grands paradigmes dans la programmation informatique :

  • L’impératif : basé sur une notion d’états modifiés par des instructions (le modèle de Turing), il s’agit de l’approche la plus répandue. Plus immédiate et intuitive, la plupart des langages informatiques utilisés (notamment les langages objet) sont basés sur ce paradigme.
  • Le déclaratif : le programme est décrit sous forme d’une description de la solution par rapport à un état initial. La programmation fonctionnelle en fait partie.

Facilement vue comme un joujou pour matheux aimant manier des concepts compliqués, la programmation fonctionnelle n’est pourtant pas incompatible avec les concepts de la programmation orienté objet. De plus, l’impératif n’a pas répondu à toutes les attentes : la concurrence, la parallélisation…
Scala, Haskell, Erlang ou Groovy sont des langages fonctionnels que vous avez pu rencontrer, mais il est possible également de coder différemment en java en utilisant quelques principes (et librairies) fonctionnels. Java 8 et la JSR 355 introduiront les bases du fonctionnel dans Java.
Voici les quatre concepts de la programmation fonctionnelle.

La pureté de la fonction

Une fonction pure, au sens mathématique, est une fonction qui ne change pas l’état du monde. Sa seule fonction est de prendre une ou des données en entrée et retourner un résultat en sortie. Quelle que soit le moment où la fonction est jouée, avec des données identiques en entrée, la sortie reste la même.
Il existe plusieurs intérêts à utiliser au maximum des fonctions pures :

  • Il n’y a aucun risque d’effet de bord à utiliser des fonctions, aucune variable cachée qui sera modifiée involontairement par cette fonction
  • Tester une fonction pure est très simple
  • La fonction peut être rejouée à l’infini : tant qu’elle recevra en paramètre la même donnée, elle retournera le même résultat, sans modifier quoi que ce soit
  • Il est également possible de ne pas jouer cette fonction, et gagner du temps si son résultat n’est pas utilisé

Nous prendrons comme exemple, afin d’illustrer chacun de ces principes, un programme permettant de calculer les occurrences suivantes de suites. Examinons la fonction ci-dessous.

fonction

On constate que cette fonction n’est pas pure. Un indice très clair est le fait qu’elle retourne “void”. Si cette fonction ne modifiait pas l’état du monde, elle ne servirait à rien. La modification se situe au niveau de la ligne : majFichier(suites);
Celle-ci met à jour les fichiers correspondant aux suites que l’on manipule. Nous voulons modifier ce fichier, il n’est donc pas question de rendre cette fonction pure. Cependant, pour tester bien plus facilement ce que nous faisons, il est possible d’extraire de cette fonction, un code qui peut être, lui intégré en fonction pure. C’est ce que nous faisons ci-dessous.

fonction-pure

La fonction calculeProchaineLigne est pure : tant que nous entrons la même chaîne de chiffres en paramètres, le retour sera identique. Elle peut donc être jouée à l’infini et être testée sans modifier l’état du monde.

L’immutabilité (ou immuabilité)

Il s’agit d’un concept allant de pair avec la pureté. Nous ne voulons pas changer l’état du monde, et ainsi nous ne changerons pas non plus l’état des paramètres passés à une fonction.
En java, nous utiliserons le mot-clef final.

Reprenons notre fonction précédente :

fonction-pure

La ligne : suite.setLigneCalculee(newLine); viole le principe d’immuabilité. D’une manière générale, l’utilisation de getter dans les classes est incompatible avec le principe d’immuabilité. Nous pouvons avoir à la place, dans notre classe Suite :

suite

De la même manière, notre liste passée en paramètre est actuellement modifiée en même temps qu’elle est parcourue. Nous allons donc créer une nouvelle liste dans laquelle nous insèrerons les nouvelles données, sans modifier la liste existante :

fonction-immutable

Ainsi, si la suite passée en paramètre est utilisée à un autre endroit du programme, aucun effet de bord n’est risqué. On gagne en simplicité de programmation.

L’expressivité

Cela correspond à l’utilisation de fonctions d’ordre supérieur, c’est-à-dire des fonctions qui prendront d’autres fonctions en paramètre.
Pour illustrer l’expressivité, nous utiliserons la bibliothèque Google Guava. Nous utiliserons l’interface Function pour définir comment obtenir un objet de la classe Suite avec la nouvelle ligne calculée à partir d’un objet de la classe Suite.

fonction-expressive-1

Nous pouvons l’utiliser grâce à la fonction transform de Google Guava qui applique une Function sur chaque élément de la liste passée en paramètre et retourne une nouvelle liste. Notre boucle est donc résumée en une seule ligne :

final List newListe = Lists.transform(suites, function);

Composabilité

Il s’agit de la capacité de composer des fonctions ensemble pour obtenir une fonction.
Ainsi, si on modifie la suite de telle manière que la ligne suivante corresponde au calcul de la prochaine ligne sur laquelle on réapplique le même calcul, on aura tout simplement dans notre fonction :

String newLine = calculeProchaineLigne(calculeProchaineLigne(suite.getLastLine()));
La pureté et l’immutabilité nous permettent de faire cela sans prendre de risque.

Loin d’être un outil exotique, la programmation fonctionnelle peut nous permettre, en utilisant ses principes, de rendre nos codes plus lisibles et plus maintenables et ce, même dans des langages orientés objet.