Le langage de programmation Haskell a été créé spécialement pour faire des calculs sur les catégories et foncteurs. Cette partie de l’article, très technique donc à ne pas lire urgemment, est consacrée à des expériences de programmation en Haskell.
Le point de vue adopté dans cet article est celui-ci : les graphes qu’on a vu sont des listes, et le langage Haskell possède des listes.
un x = [x]
main = print(un "ours")
donne l’affichage suivant :
["ours"]
On fait le choix de considérer les collections de deux objets de même type (par exemple ours), et donc l’objet deux ours est modélisé par une liste comprenant 2 fois le texte « ours » :
deux x = [x,x]
main = print(deux "ours")
donne :
La suite est facile à imaginer :
trois x = [x,x,x]
main = print(trois "ours")
donne
et ainsi de suite :
quatre x = [x,x,x,x]
main = print(quatre "ours")
donne
["ours","ours","ours","ours"]
Comment modéliser le foncteur nombre suivant ? En fait, pour obtenir le nombre qui suit un nombre vu comme liste, il faut ajouter quelque chose au début de la liste :
le_nombre_suivant nombre = "ours" : nombre
quatre = le_nombre_suivant.trois
main = print(quatre "ours")
redonne
["ours","ours","ours","ours"]
Mais
main = print(quatre "as")
donne
Il est donc temps d’aller vers l’abstrait, en évoquant des petits cailloux (représentés par la lettre o) plutôt que des ours :
un x = [x]
deux x = [x,x]
trois x = [x,x,x]
le_nombre_suivant nombre = "o" : nombre
quatre = le_nombre_suivant.trois
main = print(quatre "o")
donne
["o","o","o","o"]
Pour connaître l’écriture décimale du nombre Quatre, on peut utiliser la fonction length (cardinal en Haskell) :
main = print(length (quatre "ours"))
donne
4
Réciproquement, on peut directement construire des nombres (listes d’objets en Haskell) à partir de leur représentation décimale. Pour cela, on propose de renommer jetons les lettres o. Pour construire un objet de la catégorie Cinq, sous la forme de 5 jetons, il suffit de prélever 5 jetons parmi une réserve infinie de jetons. Haskell permet de définir une telle liste infinie :
jetons = "o":jetons
construction n = take n
cinq = construction 5
main = print(cinq jetons)
donne
["o","o","o","o","o"]
En effet l’expression cinq jetons
s’évalue avec ces étapes successives :
-
construction 5 jetons
-
take 5 jetons
(prendre 5 jetons pour constituer une collection) -
["o","o","o","o","o"]
En fait on peut encore simplifier ce code : la ligne
construction n = take n
a été obtenue en simplifiant la ligne
construction n jetons= take n jetons
(pour construire l’objet cinq jetons il faut extraire une liste de longueur 5 parmi les jetons)
mais en fait, on peut simplifier encore la ligne
construction n = take n
en constatant qu’elle ne fait que définir le mot construction comme synonyme de take :
construction = take
D’autres raccourcis sont alors possibles : choisir un mot plus court que construction (pourquoi pas prendre qui traduit littéralement take), remplacer les jetons par des batons style baguettes à calculer, et utiliser les guillemets simples pour que Haskell considère les listes comme listes de caractères, c’est-à-dire des mots (dont les lettres sont des batons) :
le_nombre_suivant nombre = '|' : nombre
batons = '|':batons
prendre = take
un = prendre 1
deux = prendre 2
trois = prendre 3
quatre = prendre 4
cinq = prendre 5
main = print(cinq batons)
donne
"|||||"
On peut définir le nombre Six comme les autres
six = prendre 6
mais aussi comme nombre suivant Cinq :
six = le_nombre_suivant.cinq
Bien entendu, on peut aussi faire l’inverse, avec
print(length (six batons))
On peut afficher 7 batons directement avec
main = print(prendre 7 batons)
Pour Guillaume Connan, les entiers ne sont pas des catégories, mais des foncteurs :
instance Functor Trois where
fmap f (Trois (x, y, z)) = Trois (f x, f y, f z)
-- un morphisme dans la catégorie des Trois
toInt :: Trois Char -> Trois Int
toInt = fmap ord
plus2 :: Trois Int -> Trois Int
plus2 = fmap (+2)
-- Pour la composition
(>=>) :: ((Trois a) -> (Trois b)) -> ((Trois b) -> (Trois c)) -> ((Trois a) -> (Trois c))
m1 >=> m2 = \ t -> m2 (m1 t)
et la catégorie Quatre :
-- Les objets sont des collections de 4 objets
data Quatre t = Quatre (t, t, t, t) deriving Show
-- On ne tient pas compte de l'ordre
instance Eq a => Eq (Quatre a) where
Quatre (x, y, z, t) == Quatre (a, b, c, d) = [x, y, z, t] \\ [a, b, c, d] == []
-- pour définir des morphismes
instance Functor Quatre where
fmap f (Quatre (x, y, z, t)) = Quatre (f x, f y, f z, f t)
toInt4 :: Quatre Char -> Quatre Int
toInt4 = fmap ord
Commentaires