Construction du nombre, catégories et foncteurs

jeudi 11 mai 2023
par  Alain BUSSER

L’idée, déjà présente chez Dedekind avec ses Kette (chaînes) est venue spontanément à des élèves ayant testé la carte des cartes : un nombre (entier positif) est un graphe tel que

  • tout sommet est relié à un unique sommet (le sommet suivant)
  • le nombre de sommets est le nombre modélisé par le graphe.

La notion (assez naturelle) d’isomorphisme entre graphes (orientés connexes sans cycle) permet alors de formaliser ce modèle dans la théorie des catégories. Dans cet article on va voir comment ladite théorie des catégories est illustrée par la construction du niombre par successeur (le successeur étant un foncteur).

Licence Creative Commons
Cet article est mis à disposition selon les termes de la Licence Creative Commons Attribution - Partage dans les Mêmes Conditions 2.0 France.

Les images ci-dessous sont cliquables et mènent à des articles en pdf détaillant les catégories évoquées, sous forme d’allusions au conte Boucle d’Or. Les graphes sont tous des DAG (orientés sans cycle) connexes.

La catégorie Un

La catégorie Un est celle des DAG sans arêtes :

La catégorie Deux

La catégorie Deux est celle des DAG n’ayant qu’une seule arête :

La catégorie Trois

La catégorie Trois est celle des DAG ayant exactement deux arêtes :

La catégorie Quatre

La catégorie Quatre est celle des DAG ayant exactement trois arêtes :

Le foncteur Nombre Suivant permet de considérer la catégorie Nombre dans laquelle

  • chacune des catégories Un, Deux, Trois etc est un objet (de la catégorie Nombre)
  • le Nombre Suivant définit un morphisme (mais alors on considère les identités, le morphisme Nombre Précédent, le suivant du suivant etc).

C’est l’histoire dont le début est raconté dans le document ci-dessus (sur la catégorie Quatre) mais la suite de l’histoire reste à raconter : la catégorie Nombre peut à son tour elle-même être considérée comme objet d’une autre catégorie, dont les autres objets, les morphismes et foncteurs vers d’autres catégories restent à étudier.

Mais ceci est une autre histoire, comme disait Papa Ours en dévorant Boucle d’Or...

Un phénomène se produit à partir du nombre Cinq : dessiner les nombres comme des chemins où le Petit Poucet (c’est un ami de Boucle d’Or, l’ignoriez-vous ?) peut semer des cailloux, est une approche nécessaire, parce qu’à partir de 5, le graphe complet n’est plus planaire. Ceci évoque une théorie mathématique de la subitisation :

en Haskell

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 :
un x = [x]

main = print(un "ours")

donne l’affichage suivant :

["ours"]

  • Deux

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 :

["ours","ours"]
  • Trois

La suite est facile à imaginer :

trois x = [x,x,x]

main = print(trois "ours")

donne

["ours","ours","ours"]
  • Quatre

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

["ours","as","as","as"]

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

  • Cinq

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 :

  1. construction 5 jetons
  2. take 5 jetons (prendre 5 jetons pour constituer une collection)
  3. ["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

"|||||"

  • Six

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))

  • Sept

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