1 Programmation fonctionnelle & Haskell
1.1 Les principes de base de la programmation fonctionnelle
1.2 Haskell
1.3 Où mène la pureté ?
2 Premiers pas vers la pure impureté
2.1 Un analogue de let dans l’esprit fonctionnel
2.2 Poussons l’analogie : composition d’actions et pérennité
2.3 Les monades comme généralisation
2.3.1 Une monade du point de vue catégorique
2.3.2 Une monade en Haskell
3 Différents usages des monades
3.1 Court-circuiter grâce à la monade Maybe
3.1.1 La monade Maybe
3.1.2 Exemple d’utilisation
3.2 Faire une boucle avec la monade des listes
3.2.1 La monade des listes
3.2.2 Exemple d’utilisation
3.3 Imiter le style impératif grâce à la monade des états
3.4 Interagir avec le monde grâce à la monade IO
Programmation fonctionnelle & Haskell
Les principes de base de la programmation fonctionnelle
Comme on l’a dit en introduction, la programmation fonctionnelle refuse toute notion de variable non- constante (mutable) et d’effets de bord : les seuls objets manipulés sont des fonctions ou des constantes :
on ne connaît pas l’assignation. Les fonctions peuvent être passées (partiellement ou non) en argument à d’autres fonctions, ou même être retournées comme valeur. Cela donne souvent une formulation très compacte et générique d’algorithmes qui seraient fastidieux à exprimer en programmation impérative. La fonction map en est un bon exemple : on appelle map avec en arguments une fonction f et une liste [a 1 ; : : : ; a n ], et on obtient en sortie la liste des résultats [f (a 1); : : : ; f (a n )].
Une des implications importante de ce genre de fonctions, dites pures est que leur absence d’effet de bord fait que leur exécution ne dépend aucunement de leur environnement d’exécution. Si on appelle une fonction plusieurs fois sur le même argument, elle rendra toujours la même réponse quelque soit l’endroit depuis lequel elle est appelée ou l’état de l’environnement d’exécution. Ainsi, si on prouve qu’une fonction pure réalise bien ce qu’on veut, on peut être certain qu’elle n’engendrera jamais aucun bug. C’est bien sûr un fait très important et cela justifie en partie tout l’intérêt qu’on prête à ce genre de paradigme de programmation : la modularité permet de prouver globalement qu’une partie d’un programme ne peut pas bugger, puisque chaque élément est correct et est garanti de l’être en toute circonstance.
Haskell
Dans la suite de cette étude, nous utiliserons le langage Haskell pour illustrer nos propos. C’est un langage purement fonctionnel né dans le milieu des années 80 justement de l’idée de regrouper sous une seule bannière les dernières avancées en date de la théorie des langages de programmation. La dernière révision du standard date de 1998. Ce langage est en plein essor et possède une communauté très active.
Haskell se place donc du côté des langages purement fonctionnels. Il en possède les caractéristiques classiques sur lesquelles ne ne reviendrons pas. La syntaxe est habituelle de ce genre de langages et le lecteur se laissera guider sans crainte. On peut tout de même dire qu’on note x : : t pour dire que x est de type t.
Où mène la pureté ?
Il est temps de se demander si tout cela mène bien à quelque chose. Cette pureté est élégante et théoriquement très satisfaisante, mais comment faire effectivement quelque chose sans boucles ni variables ?
C’est le paradoxe de la programmation fonctionnelle pure. Le code qu’on écrit est totalement exempt de tout effet de bord et pourtant on peut bien programmer ce qu’on veut. Comment ?
C’est ici que vont intervenir les monades. Elles sont en quelque sorte le pont entre le monde réel impur et le monde de la pureté fonctionnelle. C’est au sein de ces monades, et seulement à ces endroits, qu’on pourra avoir des comportements de type impurs comme l’intéraction (indispensable évidemment) avec le monde extérieur (entrées / sorties) ou des effets de bord lors de l’exécution de fonctions.
Mais tout ne s’écroule pas : l’impureté est canalisée. En effet, le programmeur peut lire simplement sur le type d’une fonction si elle est pure ou impure. Et dans ce dernier cas, selon la monade mise en jeu, il peut même savoir quels types de comportements impurs la fonctions peut présenter (entrée / sortie, écriture de fichiers, intéraction avec l’utilisateur, appels externes, effets de bord).
Premiers pas vers la pure impureté
Un analogue de let dans l’esprit fonctionnel
En Haskell, chaque fonction est constituée d’une seule expression. Il n’y a pas d’équivalent syntaxique au point-virgule de C par exemple. Et cela fait sens : si on avait “ ligne 1 ; ligne 2”, que ferait la ligne 1 ? Par définition, sa valeur de retour ne serait pas utilisée. Donc sa seule action possible est par effet de bord. Donc cette structure n’est pas autorisée dans du code pur.
Malgré tout, tous nos programmes ne sont pas que des enchaînements de one-liners (petites fonctions qui ne comportent qu’une seule ligne). Ou plutôt, on n’a pas envie de les écrire comme tels pour des raisons d’esthétique et de facilité de relecture. En effet, il est équivalent d’écrire :
— (Ce qui suit deux tirets est un commentaire en Haskell)
f :: Int -> Int -> Int — par exemple
f x y = (g x (x+y)) + (h (y*x) y) — où g et h sont des fonctions cohérentes
— ou définition équivalente :
f :: Int -> Int -> Int
f x y =
let resultatG = g x (x+y)
resultatH = h (y*x) y
in
resultatG + resultatH
Poussons l’analogie : composition d’actions et pérennité
On sent bien que ce qu’on vient de définir pour imiter le comportement de let va pouvoir se généraliser. Déjà, on peut remplacer Int par un autre type. Mais outre cela, on peut réutiliser la notion de “composition de ligne” par l’utilisation de fonctions partielles et d’une opération de composition moins triviale.
Il faut remarquer que dans l’exemple précédent, la fonction de composition >>= ne faisait que transmettre l’information à la ligne suivante, agissant seulement comme une sorte de glue. On pourrait imaginer un comportement plus complexe où le passage d’une ligne à l’autre servirait,
outre transmettre l’information de la valeur de la ligne passée, à faire remonter de l’information de la ligne qui vient. Car en effet, au final, c’est bien simplement la fonction >>= qui est évaluée et on n’est pas dans une stratégie d’exécution de la première ligne sans jamais y revenir.
Pour rendre cette idée plus concrète, nous allons construire un “logger”. C’est-à-dire que nous voulons pouvoir exécuter plusieurs lignes de code à la suite en pouvant à chaque fois enregistrer un texte qui pourrait par exemple être affiché à l’écran.
…..
Le paradoxe de la pureté en programmation fonctionnelle (574 Ko) (Cours PDF)