mdulac

home

Types, valeurs et fonctions pures dans la programmation fonctionnelle

05 Feb 2015

Généralités

Les types et les fonctions devraient être connus de tous les développeurs, car ils sont largement manipulés dans la quasi-totalité des programmes informatiques. À ce moment, je t'entends déjà répondre "je me fiche des types car j'utilise un langage faiblement typé, ou un langage typé dynamiquement !". À ce genre de remarque, il existe plusieurs réponses :

Dans ce billet, je ne parlerai pas des avantages d'un système de types statiques par rapport à un typage dynamique.

Bien que ces notions me paraissent fondamentales, je me rends compte qu'il existe des développeurs qui confondent certaines de ces notions à partir du moment où ils manipulent un langage purement fonctionnel.

Dans la suite de cet article, les exemples de code seront en Haskell

Un type A est une donnée d'un programme informatique qui définie :

Un type est donc un Set de valeurs de A, sur lesquelles peuvent être appliquées des fonctions.

Par exemple, le type Boolean est un Set de deux valeurs : True et False

Un peu de pratique

Je te propose maintenant de réécrire le type Booléen, que l'on appellera Boo, l'ensemble des valeurs T (true) et F (false) :

data Boo = T | F

et la fonction if_then_else qui prendra en arguments un Boo, et deux valeurs typées génériquement a, et :

if_then_else :: Boo -> a -> a -> a
if_then_else T x _ = x
if_then_else F _ y = y

La première ligne est le prototype de la fonction, c'est à dire sa signature, et ne comporte donc que des types ! Les autres lignes sont l'implémentation de cette même fonction, en fonction de la valeur de ses arguments (pattern-matching). Si la valeur du Boo passée à la fonction est T, on retourne x (de type a), sinon on retourne y(de type a).

Ainsi, on peut utiliser la fonction de cette manière :

if_then_else T (2 + 4) (4 + 5) -- returns 6
if_then_else F (2 + 4) (4 + 5) -- returns 9

Mais qu'est-ce que ce type a dans la signature de fonction ? Il s'agit en fait d'un type générique, c'est à dire n'importe quel type !

if_then_else T ("Hello") ("World") -- returns "Hello"
if_then_else F ("Hello") ("World") -- returns "World"

Evidemment, tous les types a doivent être le même type au moment de l'appel de la fonction. Ainsi ce code résultera en une erreur de compilation :

if_then_else T (2 + 4) ("World")

car les valeurs (2 + 3) et "World" ne sont pas du même type !

Et les fonctions pures dans tout ça ?

Une fonction pure est un traitement qui accepte un ou plusieurs arguments, et retourne une valeur en retour. La fonction ne pourra rien faire d'autre que manipuler les valeurs passées en paramètre. En d'autres termes, il lui sera impossible d'exploiter tout élement extérieur (IO, nombres aléatoires, ...), aussi appelé effet de bord.

Les arguments passés à la fonction, et sa valeur de retour sont typés. Une fonction est donc typée ! Ainsi :

J'écris avec toi une fonction sum' qui permet de sommer trois entiers passés en arguments. Cette fonction prendra trois paramètres de type entier et retournera une valeur du même type. Son type est donc (Int -> Int -> Int) -> Int, soit Int -> Int -> Int -> Int. Son implémentation sera :

sum' :: Int -> Int -> Int -> Int
sum' x y z = x + y + z

x, y et z sont des variables (donc des valeurs !) de type Int. Elles sont ensuite additionnées et retournées. Tu viens d'écrire ta deuxième fonction ! Ces variables sont additionnables car elles sont de type Int. Maintenant imaginons une fonction générique d'association, qui permettrait d'additionner deux entiers, et de concatener deux chaînes de caractères... Quel serait sont type ?

Si tu as répondu "quelque chose de générique typé a -> a -> a", alors tu as raison ! Je rassure les déçus que b -> b -> b fonctionne également ! ;)

Toi : "Et qu'en est-il de son implémentation ?" Moi : "Trouvons lui déjà un petit nom..." Toi : "J'aime beaucoup 'Bob'" Moi : "C'est pas mal, mais ça ne renseigne pas beaucoup sur ce que fait la fonction, non ?" Toi : "Alors que dis-tu de 'associate' ?" Moi : "Bien joué !"

associate :: a -> a -> a
associate x y = ???

Additionner deux entiers implique d'utiliser l'opérateur +, alors que concatener deux chaînes implique d'utiliser l'opérateur ++. Comment faire ?

Aller... je garde un peu de suspense, la réponse sera dévoilée en même temps que les Typeclass dans un prochain article :)