Programmation fonctionnelle
Introduction
Il fut un temps, dans les années 70, où la programmation fonctionnelle était réservée au seul domaine de la recherche académique avec des langages comme Lisp ou Scheme. Désormais ce n’est plus le cas, la programmation fonctionnelle s’est largement démocratisée ; on doit sans doute ce nouvel engouement à l’essor de langages comme Scala ou Haskell et aussi à l’arrivée de framework comme Play! ou bien Spark qui utilisent nativement les caractéristiques de la programmation fonctionnelle.
On verra dans ce chapitre quels sont les fondamentaux de ce paradigme de programmation, pourquoi ils se marient si bien avec les architectures modernes orientées calcul distribué avec notamment la conception de systèmes réactifs.
Programmation fonctionnelle
Dans la programmation impérative, on donne à la machine une séquence de tâches qu’elle va exécuter ; pendant qu’elle les exécute, certaines variables peuvent changer d’état, on parle alors de mutations. Lorsqu’on travaille avec la programmation fonctionnelle, on procède différemment, on ne dit pas à l’ordinateur quoi faire mais on lui décrit ce que les choses sont (leur nature en quelque sorte).
Par exemple, on peut indiquer au programme que la factorielle d’un nombre est le produit de tous les entiers de 1 jusqu’à ce nombre. On peut aussi lui dire que la somme des nombres d’une liste est la somme du premier nombre plus celle des nombres restants.
Ce type de programmation bouleverse sérieusement les habitudes du développeur impératif habitué aux systèmes à états, aux variables mutables et aux effets de bords. Nous allons voir ci-après en quoi consiste la programmation fonctionnelle au travers du langage Scala.
1. Brève introduction à Scala
Avant d’attaquer le cœur de notre sujet, voici une petite introduction au langage Scala qui servira de support à cette discussion. Nous invitons le lecteur curieux à prendre le temps de s’installer un environnement Scala en suivant les instructions disponibles à cette adresse : http://www.scala-lang.org.
Scala est un acronyme pour Scalable Language. Il peut être utilisé de la même manière pour des traitements simplistes que pour des applications critiques à large spectre. Il est notamment utilisé par Twitter, LinkedIn ou Intel. Il ressemble à un langage de script, avec une syntaxe concise et sans fioritures, tout en étant fortement typé (par inférence à la compilation). Par essence, la scalabilité de ce langage est le résultat d’une intégration précise entre concepts orientés objet et langage fonctionnel, ce qui laisse au développeur la liberté de jongler entre un style fonctionnel puriste et de la programmation objet à effets de bord.
Enfin, son interopérabilité parfaite avec Java permet la réutilisabilité des librairies existantes, l’utilisation optimale de la JVM et une exploitation...
Théorie des catégories et design patterns
Résumons rapidement les principes de la programmation fonctionnelle que nous avons formulés à la section précédente :
-
Les fonctions sont des éléments autonomes, pas forcément attachés à des classes.
-
La composition des fonctions est partout, les petites fonctions fabriquent les grandes. La composition est fractale.
-
Les types ne sont pas (que) des classes, ils peuvent être des fonctions, des comportements, des données composites.
Malgré certains préceptes que nous avons évoqués, comme l’application partielle, la gestion des options, les boucles for-comprehension…, il n’existe pas de patrons de conceptions clairement formalisés comme ceux du GOF (voir chapitre sur les design patterns) pour la programmation fonctionnelle. Toutefois, on peut voir les monoïdes et les monades comme une certaine forme de design patterns.
1. Monoïdes
Selon Wikipédia : « En mathématiques, et plus précisément en algèbre générale, un monoïde est une structure algébrique consistant en un ensemble muni d’une loi de composition interne associative et d’un élément neutre. »
Par définition il s’agit donc d’un magma associatif unifère (un ensemble muni d’une loi associative possédant un élément neutre), soit un demi-groupe unifère.
Le monoïde en programmation fonctionnelle est plutôt une approche pour travailler d’une même façon avec beaucoup de types différents de valeurs. Le concept mathématique défini ci-avant peut sembler rébarbatif, mais il n’est pas essentiel de le connaître pour programmer avec les monoïdes.
a. Quelques exemples mathématiques simples
Voici la première équation :
1 + 3 = 4
Jusqu’ici tout va bien.
1 + (3 + 4) = (1 + 3) + 4
En voici une autre plutôt éclairante :
2 + 0 = 2 et 0 + 2 = 2
Nous avons ici toutes les mathématiques nécessaires à la compréhension d’un monoïde. En effet, dans ces trois équations résident des concepts mathématiques permettant de généraliser des concepts.
b. Généralisation...
Systèmes réactifs
Le manifeste Réactif est une réponse aux problèmes de conception des applications modernes scalables. Il encourage les développeurs à rendre leurs systèmes souples, disponibles, résilients et orientés messages. La programmation fonctionnelle est très adaptée aux contraintes définies par les quatre axes des systèmes réactifs.
Figure 14.1 : Le manifeste réactif (source : http://www.reactivemanifesto.org/fr)
1. Reactive manifesto
Voici un extrait du manifeste Réactif publié le 16 septembre 2014 par Jonas Bonér, Dave Farley, Roland Kuhn et Martin Thompson. Les vertus architecturales des systèmes réactifs sont indéniables et méritent l’attention de toute personne qui conçoit un système destiné à traiter de grosses volumétries de données et/ou un nombre croissant d’utilisateurs.
« … Les Systèmes Réactifs sont :
-
Disponibles (en anglais : responsive) : le système répond rapidement en toutes circonstances, si cela est possible. Cette disponibilité à répondre est primordiale pour le bon fonctionnement et l’utilisation d’un système, mais par-dessus tout, elle est basée sur la capacité d’un système à détecter rapidement des erreurs et à y répondre de façon effective. Ce type de système est en mesure d’assurer une qualité de service constante, reposant sur des temps de réponse minimaux et cohérents. C’est ce comportement cohérent qui permet de traiter plus simplement les cas d’erreurs et de gagner la confiance des utilisateurs, encourageant ainsi une interaction accrue avec le système.
-
Résilients (en anglais : resilient) : le système reste disponible en cas d’erreur. Ce comportement ne se limite pas aux systèmes critiques - à partir du moment où un système n’est plus en mesure de répondre après une panne, il ne s’agit pas d’un système robuste. Cette robustesse peut être atteinte grâce à la réplication de fonctionnalité, l’atténuation de cas d’erreurs, l’isolement de composants et la délégation...