[go: nahoru, domu]

Aller au contenu

« Type algébrique de données » : différence entre les versions

Un article de Wikipédia, l'encyclopédie libre.
Contenu supprimé Contenu ajouté
Maëlan (discuter | contributions)
Aucun résumé des modifications
Quaivingt (discuter | contributions)
Fonctionnalité de suggestions de liens : 3 liens ajoutés.
Balises : Éditeur visuel Modification par mobile Modification par le web mobile Modification sur mobile avancée Tâche pour novices Suggéré : ajouter des liens
 
(13 versions intermédiaires par 5 utilisateurs non affichées)
Ligne 1 : Ligne 1 :
{{Voir homonymes|Algébrique}}
{{en travaux|Maëlan|7 juillet 2017}}
{{Ébauche|informatique}}
{{Ébauche|informatique}}

Un '''type algébrique''' est une forme de [[type de données]] composite{{note|groupe=note|texte=C’est‐à‐dire un type formé en combinant d’autres types plus simples.}}, qui combine les fonctionnalités des '''types produits''' ({{math|''n''}}‐uplets ou enregistrements) et des '''types sommes''' ([[union disjointe]]). Combinée à la [[type récursif|récursivité]], elle permet d’exprimer les données structurées telles que les listes et les arbres.
Un '''type algébrique''' est une forme de [[type de données]] composite{{note|groupe=note|texte=C’est‐à‐dire un type formé en combinant d’autres types plus simples.}}, qui combine les fonctionnalités des '''types produits''' ({{math|''n''}}‐uplets ou enregistrements) et des '''types sommes''' ([[union disjointe]]). Combinée à la [[type récursif|récursivité]], elle permet d’exprimer les données structurées telles que les listes et les arbres.


Ligne 7 : Ligne 8 :
=== Type produit ===
=== Type produit ===


Le '''type produit''' de deux types {{math|''A''}} et {{math|''B''}} est l’analogue en [[théorie des types]] du [[produit cartésien]] [[théorie des ensembles|ensembliste]] et est noté {{math|''A'' × ''B''}}. C’est le type des couples dont la première composante est de type {{math|''A''}} et la seconde de type {{math|''B''}}. Deux [[fonction (informatique)|fonctions]] canoniques lui sont associées, appelées ''projections'', donnant la valeur de chaque composante.
Le '''type produit''' de deux types {{mvar|A}} et {{mvar|B}} est l’analogue en [[théorie des types]] du [[produit cartésien]] [[théorie des ensembles|ensembliste]] et est noté {{math|{{mvar|A}} × {{mvar|B}}}}. C’est le type des couples dont la première composante est de type {{mvar|A}} et la seconde de type {{mvar|B}}. Deux [[fonction (informatique)|fonctions]] canoniques lui sont associées, appelées ''projections'', donnant la valeur de chaque composante.


{{exemple|nom=Exemple : type produit en [[OCaml]]|
{{exemple|nom=Exemple : type produit en [[OCaml]]|
On peut définir en langage OCaml le type d’une entrée de [[tableau associatif|dictionnaire]] :
On peut définir en langage OCaml le type d’une entrée de [[tableau associatif|dictionnaire]] :


<source lang="OCaml">
<syntaxhighlight lang="OCaml">
type dict_entry = string * int
type dict_entry = string * int


let entry = ("clé", 37)
let entry = ("clé", 37)


(* get_value : dict_entry -> int *)
let get_value (key, value) = value
let get_value (key, value) = value
</syntaxhighlight>
</source>
}}
}}


Le produit se généralise naturellement à un nombre quelconque d’opérandes, pour donner des types de {{math|''n''}}‐uplets. Dans le cas particulier du [[produit vide]], le type des 0‐uplets est nommé '''[[type unité]]''' et noté {{math|'''1'''}} : c’est l’élément neutre du produit et il contient une unique valeur, souvent notée {{math|()}}.
Le produit se généralise naturellement à un nombre quelconque d’opérandes, pour donner des types de {{mvar|n}}‐uplets. Dans le cas particulier du [[produit vide]], le type des 0‐uplets est nommé '''[[type unité]]''' et noté {{math|'''1'''}} : c’est l’[[élément neutre]] du produit et il contient une unique valeur, souvent notée {{math|()}}.


Des considérations pratiques amènent souvent à nommer les composantes{{note|groupe=note|texte=De ''[[typage structurel|structurel]]'', le typage devient alors ''[[typage nominatif|nominal]]''. Dans le premier cas, l’expression d’un {{math|''n''}}‐uplet permet de déduire entièrement sa structure (par exemple, <code>("clé", 37)</code> est de type <code>string * int</code>) et déclarer le type est donc superflu. Dans le second cas, au contraire, l’expression ne suffit pas (<code>{ key = "clé" ; value = 37 }</code> peut suivre la structure <code>{ key : string ; value : int }</code> mais aussi <code>{ value : int ; key : string }</code> {{incise|qui est différente}}, et l’expression <code>entry.value</code> permet seulement de déduire que la structure de <code>entry</code> contient un champ nommé <code>value</code>), et il faut donc déclarer les structures utilisées afin d’associer chaque nom de membre à une structure.}}. Dans ce contexte, le type est souvent appelé ''structure'' et ses valeurs des ''enregistrements''<!-- référence à ajouter : traduction française du Dragon Book--> ; les composantes sont appelées ''membres'', et la projection selon le membre <code>m</code> s’écrit avec une notation suffixe <code>.m</code>.
Des considérations pratiques amènent souvent à nommer les composantes{{note|groupe=note|texte=De ''[[typage structurel|structurel]]'', le typage devient alors ''[[typage nominatif|nominal]]''. Dans le premier cas, l’expression d’un {{mvar|n}}‐uplet permet de déduire entièrement sa structure (par exemple, <code>("clé", 37)</code> est de type <code>string * int</code>) et déclarer le type est donc superflu. Dans le second cas, au contraire, l’expression ne suffit pas (<code>{ key = "clé" ; value = 37 }</code> peut suivre la structure <code>{ key : string ; value : int }</code> mais aussi <code>{ value : int ; key : string }</code> {{incise|qui est différente}}, et l’expression <code>entry.value</code> permet seulement de déduire que la structure de <code>entry</code> contient un champ nommé <code>value</code>), et il faut donc déclarer les structures utilisées afin d’associer chaque nom de membre à une structure.}}. Dans ce contexte, le type est souvent appelé ''structure'' et ses valeurs des ''enregistrements''<!-- référence à ajouter : traduction française du Dragon Book --> ; les composantes sont appelées ''membres'', et la projection selon le membre <code>m</code> s’écrit avec une notation suffixe <code>.m</code>.


{{exemple|nom=Exemple : structure en OCaml|
{{exemple|nom=Exemple : structure en OCaml|
Toujours en OCaml, l’exemple précédent s’adapte ainsi :
Toujours en OCaml, l’exemple précédent s’adapte ainsi :


<source lang="OCaml">
<syntaxhighlight lang="OCaml">
type dict_entry = {
type dict_entry = {
key : string ;
key : string ;
Ligne 36 : Ligne 38 :
let entry = { key = "clé" ; value = 37 }
let entry = { key = "clé" ; value = 37 }


(* get_value : dict_entry -> int *)
let get_value entry = entry.value
let get_value entry = entry.value
</syntaxhighlight>
</source>
}}
}}


{{exemple|nom=Exemple : structure en [[langage C]]|
{{exemple|nom=Exemple : structure en [[langage C]]|
Cette fonctionnalité se traduit en langage C par {{lien|lang=en|trad=struct (C programming language)|struct (langage C)|texte=le mot‐clé <code>struct</code>}} :
Cette fonctionnalité se traduit en langage C par {{lien|lang=en|trad=struct (C programming language)|struct (langage C)|texte=le mot‐clé <code>struct</code>}} :


<source lang="C">
<syntaxhighlight lang="C">
typedef struct {
typedef struct {
char* key ;
char* key ;
Ligne 54 : Ligne 57 :
return entry.value ;
return entry.value ;
}
}
</syntaxhighlight>
</source>
}}
}}


{{article détaillé|enregistrement (programmation informatique)}}
{{article détaillé|enregistrement (structure de données)}}


=== Type somme ===
=== Type somme ===


<!-- [[:en:Tagged union]] est très développée, il faudrait peut‐être importer son contenu quelque part. -->
Le '''type somme''' de deux types {{math|''A''}} et {{math|''B''}} est l’analogue en théorie des types de l’[[union disjointe]] ensembliste et est noté {{math|''A'' + ''B''}}. Il représente un type contenant toutes les valeurs de chacun des deux types {{math|''A''}} et {{math|''B''}}, de telle sorte qu’une valeur de type {{math|''A''}} ne puisse pas être confondue avec une valeur de type {{math|''B''}} (même si {{math|§=''A'' = ''B''}}).


Le '''type somme''' de deux types {{mvar|A}} et {{mvar|B}} est l’analogue en théorie des types de l’[[union disjointe]] ensembliste et est noté {{math|{{mvar|A}} + {{mvar|B}}}}. Il représente un type contenant toutes les valeurs de chacun des deux types {{mvar|A}} et {{mvar|B}}, de telle sorte qu’une valeur issue de {{mvar|A}} ne puisse pas être confondue avec une valeur issue de {{mvar|B}} (même si {{math|§={{mvar|A}} = {{mvar|B}}}}).
En théorie des ensembles, on représenterait la somme par l’ensemble {{math|{1}×''A'' ∪ {2}×''B''}} ; la première composante (1 ou 2) d’un tel objet est une étiquette qui indique si cet objet se trouve dans le bras de gauche ({{math|''A''}}) ou dans le bras de droite ({{math|''B''}}) de la somme. Les analogues en théorie des types des expressions {{math|(1, ''a'')}} et {{math|(2, ''b'')}} sont souvent notés {{math|ι₁ ''a''}} et {{math|ι₂ ''b''}}. Ces notations {{math|ι₁}} et {{math|ι₂}} peuvent être vues comme des fonctions [[fonction injective|injectives]], respectivement de {{math|''A''}} dans {{math|''A'' + ''B''}} et de {{math|''B''}} dans {{math|''A'' + ''B''}}, qui permettent de construire les valeurs de la somme, d’où leur nom de ''constructeurs''. Dans {{math|ι₁ ''a''}}, la valeur {{math|''a''}} est appelée l’''argument'' du constructeur {{math|ι₁}}.

En théorie des ensembles, on représenterait la somme par l’ensemble {{math|{1}×{{mvar|A}} ∪ {2}×{{mvar|B}}}} ; la première composante (1 ou 2) d’un tel objet est une étiquette qui indique si cet objet se trouve dans le bras de gauche ({{mvar|A}}) ou dans le bras de droite ({{mvar|B}}) de la somme. Les analogues en théorie des types des expressions {{math|(1, {{mvar|a}})}} et {{math|(2, {{mvar|b}})}} sont souvent notés {{math|ι{{ind|1}} {{mvar|a}}}} et {{math|ι{{ind|2}} {{mvar|b}}}} ({{math|ι}} est la lettre grecque ''iota''). Ces notations {{math|ι{{ind|1}}}} et {{math|ι{{ind|2}}}} peuvent être vues comme des fonctions [[fonction injective|injectives]], respectivement de {{mvar|A}} dans {{math|{{mvar|A}} + {{mvar|B}}}} et de {{mvar|B}} dans {{math|{{mvar|A}} + {{mvar|B}}}}, qui permettent de construire les valeurs de la somme, d’où leur nom de ''constructeurs''. Dans {{math|ι{{ind|1}} {{mvar|a}}}}, la valeur {{mvar|a}} est appelée l’''argument'' du constructeur {{math|ι{{ind|1}}}}.


Traiter des valeurs d’un type somme requiert un raisonnement par cas, nommé dans ce contexte ''[[filtrage par motif]]''. Chaque bras {{incise|qu’on reconnaît par son constructeur et dont on peut récupérer la valeur puisque ce constructeur est injectif}} fait l’objet d’un cas séparé.
Traiter des valeurs d’un type somme requiert un raisonnement par cas, nommé dans ce contexte ''[[filtrage par motif]]''. Chaque bras {{incise|qu’on reconnaît par son constructeur et dont on peut récupérer la valeur puisque ce constructeur est injectif}} fait l’objet d’un cas séparé.


{{exemple|nom=Exemple : type somme en OCaml|
{{exemple|nom=Exemple : type somme en OCaml|
On peut définir on OCaml l’union disjointe des nombres entiers et des nombres flottants et définir par filtrage une fonction sur cette union :
On peut définir on OCaml l’union disjointe des nombres entiers et des nombres flottants et définir par filtrage une fonction sur cette union :


<source lang="OCaml">
<syntaxhighlight lang="OCaml">
type sum = Int of int | Float of float
type sum = Int of int | Float of float


(* print : sum -> unit *)
let print = function
let print = function
| Int i -> Printf.printf "%i" i
| Int i -> Printf.printf "%i" i
| Float f -> Printf.printf "%f" f
| Float f -> Printf.printf "%f" f
</syntaxhighlight>
</source>


Ici, les constructeurs sont nommés <code>Int</code> et <code>Float</code>.
Ici, les constructeurs sont nommés <code>Int</code> et <code>Float</code>.
Ligne 82 : Ligne 88 :


{{exemple|nom=Exemple : type « union » en langage C|
{{exemple|nom=Exemple : type « union » en langage C|
Cette fonctionnalité s’approxime en langage C avec {{lien|lang=en|trad=Union type#C/C++|union (langage C)|texte=le mot clé <code>union</code>}} à condition d’y adjoindre une étiquette, mais cela n’offre pas les mêmes garanties (on peut lire et modifier un objet du type somme en faisant fi de son étiquette quitte à provoquer des [[bug informatique|bugs]]) :
Cette fonctionnalité s’approxime en langage C avec {{lien|lang=en|trad=Union type#C/C++|union (langage C)|texte=le mot clé <code>union</code>}} à condition d’y adjoindre une étiquette, mais cela n’offre pas les mêmes garanties (on peut lire et modifier un objet du type somme en faisant fi de son étiquette {{incise|quitte à provoquer des [[bug informatique|bugs]]|stop}}) :


<source lang="C">
<syntaxhighlight lang="C">
typedef struct {
typedef struct {
enum { INT, FLOAT } tag ;
enum { INT, FLOAT } tag ;
Ligne 99 : Ligne 105 :
printf("%f", x.f) ;
printf("%f", x.f) ;
}
}
</syntaxhighlight>
</source>
}}
}}


La somme se généralise naturellement à un nombre quelconque d’opérandes. Dans le cas particulier de la [[somme vide]], le type est nommé '''[[type vide]]''' et noté {{math|'''0'''}} : c’est l’élément neutre de la somme (et [[élément absorbant]] du produit) et il ne contient aucune valeur.
La somme se généralise naturellement à un nombre quelconque d’opérandes. Dans le cas particulier de la [[somme vide]], le type est nommé '''[[type vide]]''' et noté {{math|'''0'''}} : c’est l’élément neutre de la somme (et [[élément absorbant]] du produit) et il ne contient aucune valeur.


=== Type algébrique ===
=== Type énuméré ===

Un '''type algébrique''' est une somme de produits, et généralise donc ces deux notions.

Des cas spéciaux de types algébriques sont les types produits (un seul constructeur), les types sommes (un seul argument pour chaque constructeur) et les [[type énuméré|types énumérations]] (plusieurs constructeurs sans argument).


Un '''type énuméré''' représente un ensemble fini, dont les éléments sont les différents constructeurs. Définir une fonction dessus revient à définir l’image de chaque élément, individuellement.
Un '''type énuméré''' représente un [[ensemble fini]], dont les éléments sont les différents constructeurs. Définir une fonction dessus revient à définir l’image de chaque élément, individuellement.


{{exemple|nom=Exemple : type énuméré en OCaml|
{{exemple|nom=Exemple : type énuméré en OCaml|
On peut par exemple coder l’ensemble des quatre [[enseigne (carte à jouer)|couleurs d’un jeu de cartes classique]] :
On peut par exemple coder l’ensemble des quatre [[enseigne (carte à jouer)|couleurs d’un jeu de cartes classique]] :


<source lang="OCaml">
<syntaxhighlight lang="OCaml">
type couleur = Coeur | Carreau | Trefle | Pique
type couleur = Coeur | Carreau | Trefle | Pique


(* nom_de_la_couleur : couleur -> string *)
let nom_de_la_couleur = function
let nom_de_la_couleur = function
| Coeur -> "♥ cœur"
| Coeur -> "♥ cœur"
Ligne 123 : Ligne 126 :
| Trefle -> "♣ trèfle"
| Trefle -> "♣ trèfle"
| Pique -> "♠ pique"
| Pique -> "♠ pique"
</syntaxhighlight>
</source>
}}
}}


Ligne 129 : Ligne 132 :
Cette fonctionnalité se traduit en langage C par le mot‐clé <code>enum</code> :
Cette fonctionnalité se traduit en langage C par le mot‐clé <code>enum</code> :


<source lang="C">
<syntaxhighlight lang="C">
typedef enum { COEUR, CARREAU, TREFLE, PIQUE } couleur ;
typedef enum { COEUR, CARREAU, TREFLE, PIQUE } couleur ;


Ligne 140 : Ligne 143 :
}
}
}
}
</syntaxhighlight>
</source>
}}
}}


{{article détaillé|type énuméré}}
Des types algébriques courants dans la pratique incluent le {{lien|lang=en|trad=option type|type option|texte=type option}}. Il permet d’ajouter à un type donné une valeur spéciale, considérée comme « indéfinie » ou comme valeur d’erreur (l’équivalent de <code>null</code> dans certains langages de programmation), ce qui permet de définir des fonctions partielles de façon contrôlée.


=== Type algébrique ===
{{exemple|nom=Exemple : type option en OCaml|

<source lang="OCaml">
Un '''type algébrique''' est une somme de produits, et généralise donc ces deux notions.

Ainsi, des cas spéciaux de types algébriques sont les types produits (un seul constructeur), les types sommes (un seul argument pour chaque constructeur) et les types énumérations (plusieurs constructeurs sans argument).

{{exemple|nom=Exemple : type option et type résultat|
Les {{lien|lang=en|trad=option type|type option|texte=types options}} sont des applications courantes de types algébriques. Ils permettent d’ajouter à un type donné une valeur spéciale, considérée comme « indéfinie » ou comme valeur d’erreur (l’équivalent de <code>null</code> dans certains langages de programmation), ce qui permet de définir des fonctions partielles de façon contrôlée.

La valeur spéciale est représentée par un constructeur <code>None</code> qui ne prend aucun argument, tandis que les valeurs du type à compléter sont enveloppées dans un constructeur <code>Some</code> (qui prend donc un argument de ce type).

<syntaxhighlight lang="OCaml">
type int_option = None | Some of int
type int_option = None | Some of int


(* division : int -> int -> int_option *)
let division x y =
let division x y =
if y = 0 then
if y = 0 then
Ligne 154 : Ligne 168 :
else
else
Some (x / y)
Some (x / y)
</syntaxhighlight>
</source>
}}


On peut perfectionner le mécanisme en agrémentant le cas d’erreur d’un message de description (donnée de type <code>string</code>).
On peut perfectionner le mécanisme en agrémentant le cas d’erreur d’un message de description (donnée de type <code>string</code>).


<syntaxhighlight lang="OCaml">
{{exemple|nom=Exemple : type résultat en OCaml|
<source lang="OCaml">
type int_result = Result of int | Error of string
type int_result = Result of int | Error of string


(* division : int -> int -> int_result *)
let division x y =
let division x y =
if y = 0 then
if y = 0 then
Ligne 168 : Ligne 181 :
else
else
Result (x / y)
Result (x / y)
</syntaxhighlight>
</source>
}}
}}


== Exemple du type liste ==
== Polymorphisme ==


{{section vide}}
Un des exemples les plus courants de type algébrique est le type liste, défini par deux constructeurs :
* <code>Nil</code>, aussi noté <code>[]</code>, qui désigne la liste vide,
* et <code>Cons</code> (abréviation de « constructeur »), aussi noté <code>:</code> ou <code>::</code>, qui désigne la combinaison d’un élément et d’une liste plus courte.
Par exemple, <code>Cons 1 (Cons 2 (Cons 3 (Cons 4 Nil)))</code>, aussi noté <code>1 : 2 : 3 : 4 : []</code>, est la liste constituée des quatre éléments 1, 2, 3, 4, dans cet ordre.


Dans les [[langage de programmation|langages]] qui les supportent, les types algébriques peuvent être [[polymorphisme (informatique)|(paramétriquement) polymorphes]], ce qui permet la [[programmation générique]]. Ainsi, la définition d’un type algébrique peut être paramétrée par des variables de types.
== Abstraction ==


On peut alors définir des fonctions génériques agissant sur de tels types polymorphes.
Un type algébrique de données peut être aussi un [[type abstrait]] de données (sigle anglais : ADT) s'il est exporté à partir d'un module sans ses constructeurs. Les valeurs d'un tel type peuvent être manipulées seulement avec des fonctions définies dans le même module que le type lui-même.


{{exemple|nom=Exemple : type option polymorphe|
== Un exemple ==
On peut rendre polymorphe la définition du type option vue précédemment. Ça s’écrit ainsi en langage OCaml (où <code>'a</code> dénote une variable de type) :


<syntaxhighlight lang="OCaml">
En [[Haskell]], on peut définir un nouveau type algébrique de données, <code>Tree</code> :
type 'a option = None | Some of 'a


(** Utilisation d’instances du type polymorphe : **)
<source lang="haskell">
data Tree = Empty
| Leaf Int
| Node Tree Tree
</source>
ou dans la syntaxe [[OCaml]] :
<source lang="ocaml">
type tree = Empty
| Leaf of int
| Node of tree * tree
</source>
Ici, <code>Empty</code>, <code>Leaf</code> et <code>Node</code> sont des constructeurs.
De manière similaire à une fonction, un constructeur est appliqué aux arguments du type approprié, donnant une instance du type de données
auquel le constructeur appartient. Par exemple <code>Leaf</code> a un type fonctionnel <code>Int -> Tree</code>.
Cela signifie qu'étant donné un entier comme argument à <code>Leaf</code> produit une valeur de type <code>Tree</code>.
Comme <code>Node</code> prend deux arguments du type <code>Tree</code> lui-même, le type est [[type récursif]].


(* int_division : int -> int -> int option *)
Les opérations sur les types algébriques de données peuvent être définies par le [[filtrage par motif]] pour retrouver les
let int_division x y =
arguments. Par exemple, considérons une fonction pour calculer la profondeur d'une <code>Tree</code> :
if y = 0 then
<source lang="haskell">
None
else
Some (x / y)


(* float_division : float -> float -> float option *)
depth :: Tree -> Int
let float_division x y =
depth Empty = 0
if y = 0.0 then
depth (Leaf n) = 1
None
depth (Node l r) = 1 + max (depth l) (depth r)
else
</source>
Some (x /. y)


(** Définition d’une fonction générique : **)
Donc, un <code>Tree</code> donné à la fonction <code>depth</code> peut
être aussi construit pour chacun d'eux pour traiter tous les cas. Dans
les cas de <code>Node</code>, le motif extrait les sous-arbres
<code>l</code> et <code>r</code> pour un traitement ultérieur.


(* get_value : 'a -> 'a option -> 'a *)
== Polymorphisme ==
let get_value default_value optional_value =

match optional_value with
{{section vide}}
| None -> default_value
| Some value -> value
</syntaxhighlight>
}}


=== Type algébrique généralisé {{ancre|GADT}} ===
=== Type algébrique généralisé {{ancre|GADT}} ===
Ligne 231 : Ligne 233 :


{{section vide}}
{{section vide}}

=== Listes ===

Un des exemples les plus importants de type algébrique est le type [[liste (informatique)|liste]], défini de façon récursive par deux constructeurs :
* {{math|Nil}}, aussi noté <code>[]</code>, qui désigne la liste vide,
* et {{math|Cons}} (abréviation de « constructeur »), aussi noté <code>::</code> ou <code>:</code>, qui désigne la combinaison d’un élément et d’une liste plus courte.
Par exemple, {{math|Cons 1 (Cons 2 (Cons 3 (Cons 4 Nil)))}}, aussi noté <code>1 :: 2 :: 3 :: 4 :: []</code>, est la liste constituée des quatre éléments 1, 2, 3, 4, dans cet ordre.

Toutes les opérations sur les listes se définissent alors par récurrence, en utilisant le filtrage par motif. Par exemple, pour calculer la longueur d’une liste :
* la longueur de la liste vide ({{math|Nil}}) est zéro,
* et la longueur d’une liste de la forme {{math|Cons {{mvar|x}} {{mvar|suite}}}} est un plus la longueur de la liste {{mvar|suite}}.

{{exemple|nom=Exemple : type liste en OCaml|
Cette définition se traduit ainsi en langage OCaml :
<syntaxhighlight lang="OCaml">
type 'a list =
| Nil
| Cons of 'a * 'a list

let list1234 = Cons 1 (Cons 2 (Cons 3 (Cons 4 Nil)))

let rec length = function
| Nil -> 0
| Cons x s -> 1 + length s
</syntaxhighlight>
}}

=== Arbres ===

Les types algébriques permettent également de définir des structures d’[[arbre (informatique)|arbres]]. Un [[arbre binaire]] peut se construire au moyen de deux constructeurs :
* {{math|Leaf {{mvar|e}}}} qui désigne une feuille d’étiquette {{mvar|e}},
* et {{math|Node {{mvar|e}} {{mvar|g}} {{mvar|d}}}} qui désigne un nœud interne d’étiquette {{mvar|e}}, de fils gauche {{mvar|g}} et de fils droit {{mvar|d}}.

Par exemple,
<pre>
Node 1
(Node 2
(Leaf 4)
(Node 5
(Leaf 6)
(Leaf 7)
)
)
(Leaf 3)
</pre>
est l’arbre suivant :
<pre>
1
/ \
2 3
/ \
4 5
/ \
6 7
</pre>

Comme pour les listes, les opérations sur les arbres se définissent par récurrence. Par exemple, pour calculer la hauteur d’un arbre :
* la hauteur d’une feuille est un,
* et la hauteur d’un nœud interne est un plus le maximum de la hauteur de ses deux fils.

{{exemple|nom=Exemple : type arbre binaire en OCaml|
Cette définition se traduit ainsi en langage OCaml :
<syntaxhighlight lang="OCaml">
type tree =
| Leaf of int
| Node of tree * int * tree

let my_tree = Node 1 (Node 2 (Leaf 4) (Node 5 (Leaf 6) (Leaf 7))) (Leaf 3)

let rec height = function
| Leaf e -> 1
| Node e l r -> 1 + max (height l) (height r)
</syntaxhighlight>
}}

== Abstraction ==

Un type algébrique peut être [[type abstrait|abstrait]] : il suffit pour ça de ne pas exposer sa structure interne (ses constructeurs et leurs divers champs). Ainsi, il ne peut être manipulé que par les fonctions prévues à cet effet, et son implémentation peut être changée.

C’est une technique fréquente car les types algébriques permettent de réaliser des structures de données complexes.


== Voir aussi ==
== Voir aussi ==


<!--
*[[Tagged union]]
*[[Disjoint union]]
-->
* [[Type (informatique)|Type]]
* [[Type (informatique)|Type]]
* [[Type récursif]]
* [[Filtrage par motif]]
* [[Enregistrement (structure de données)|Enregistrement]]
* [[Type énuméré|Énumération]]
* {{lien|lang=en|trad=Option type|Type option}}
* [[Liste (informatique)|Liste]]
* [[Arbre (informatique)|Arbre]]


== Notes & références ==
== Notes & références ==

Dernière version du 17 janvier 2024 à 20:29

Un type algébrique est une forme de type de données composite[note 1], qui combine les fonctionnalités des types produits (n‐uplets ou enregistrements) et des types sommes (union disjointe). Combinée à la récursivité, elle permet d’exprimer les données structurées telles que les listes et les arbres.

Définitions[modifier | modifier le code]

Type produit[modifier | modifier le code]

Le type produit de deux types A et B est l’analogue en théorie des types du produit cartésien ensembliste et est noté A × B. C’est le type des couples dont la première composante est de type A et la seconde de type B. Deux fonctions canoniques lui sont associées, appelées projections, donnant la valeur de chaque composante.

Exemple : type produit en OCaml :

On peut définir en langage OCaml le type d’une entrée de dictionnaire :

type dict_entry = string * int

let entry = ("clé", 37)

(* get_value : dict_entry -> int *)
let get_value (key, value) = value

Le produit se généralise naturellement à un nombre quelconque d’opérandes, pour donner des types de n‐uplets. Dans le cas particulier du produit vide, le type des 0‐uplets est nommé type unité et noté 1 : c’est l’élément neutre du produit et il contient une unique valeur, souvent notée ().

Des considérations pratiques amènent souvent à nommer les composantes[note 2]. Dans ce contexte, le type est souvent appelé structure et ses valeurs des enregistrements ; les composantes sont appelées membres, et la projection selon le membre m s’écrit avec une notation suffixe .m.

Exemple : structure en OCaml :

Toujours en OCaml, l’exemple précédent s’adapte ainsi :

type dict_entry = {
  key   : string ;
  value : int ;
}

let entry = { key = "clé" ; value = 37 }

(* get_value : dict_entry -> int *)
let get_value entry = entry.value
Exemple : structure en langage C :

Cette fonctionnalité se traduit en langage C par le mot‐clé struct (en) :

typedef struct {
	char* key ;
	int   value ;
} dict_entry ;

dict_entry entry = { .key = "clé", .value = 37 } ;

int get_value (dict_entry entry) {
	return entry.value ;
}

Type somme[modifier | modifier le code]

Le type somme de deux types A et B est l’analogue en théorie des types de l’union disjointe ensembliste et est noté A + B. Il représente un type contenant toutes les valeurs de chacun des deux types A et B, de telle sorte qu’une valeur issue de A ne puisse pas être confondue avec une valeur issue de B (même si A = B).

En théorie des ensembles, on représenterait la somme par l’ensemble {1}×A ∪ {2}×B ; la première composante (1 ou 2) d’un tel objet est une étiquette qui indique si cet objet se trouve dans le bras de gauche (A) ou dans le bras de droite (B) de la somme. Les analogues en théorie des types des expressions (1, a) et (2, b) sont souvent notés ι1 a et ι2 b (ι est la lettre grecque iota). Ces notations ι1 et ι2 peuvent être vues comme des fonctions injectives, respectivement de A dans A + B et de B dans A + B, qui permettent de construire les valeurs de la somme, d’où leur nom de constructeurs. Dans ι1 a, la valeur a est appelée l’argument du constructeur ι1.

Traiter des valeurs d’un type somme requiert un raisonnement par cas, nommé dans ce contexte filtrage par motif. Chaque bras — qu’on reconnaît par son constructeur et dont on peut récupérer la valeur puisque ce constructeur est injectif — fait l’objet d’un cas séparé.

Exemple : type somme en OCaml :

On peut définir on OCaml l’union disjointe des nombres entiers et des nombres flottants et définir par filtrage une fonction sur cette union :

type sum = Int of int | Float of float

(* print : sum -> unit *)
let print = function
  | Int i   -> Printf.printf "%i" i
  | Float f -> Printf.printf "%f" f

Ici, les constructeurs sont nommés Int et Float.

Exemple : type « union » en langage C :

Cette fonctionnalité s’approxime en langage C avec le mot clé union (en) à condition d’y adjoindre une étiquette, mais cela n’offre pas les mêmes garanties (on peut lire et modifier un objet du type somme en faisant fi de son étiquette — quitte à provoquer des bugs) :

typedef struct {
	enum { INT, FLOAT } tag ;
	union {
		int i ;
		float f ;
	} ;
} sum_t ;

void print (sum_t x) {
	if (x.tag == INT)
		printf("%i", x.i) ;
	else if (x.tag == FLOAT)
		printf("%f", x.f) ;
}

La somme se généralise naturellement à un nombre quelconque d’opérandes. Dans le cas particulier de la somme vide, le type est nommé type vide et noté 0 : c’est l’élément neutre de la somme (et élément absorbant du produit) et il ne contient aucune valeur.

Type énuméré[modifier | modifier le code]

Un type énuméré représente un ensemble fini, dont les éléments sont les différents constructeurs. Définir une fonction dessus revient à définir l’image de chaque élément, individuellement.

Exemple : type énuméré en OCaml :

On peut par exemple coder l’ensemble des quatre couleurs d’un jeu de cartes classique :

type couleur = Coeur | Carreau | Trefle | Pique

(* nom_de_la_couleur : couleur -> string *)
let nom_de_la_couleur = function
  | Coeur   -> "♥ cœur"
  | Carreau -> "♦ carreau"
  | Trefle  -> "♣ trèfle"
  | Pique   -> "♠ pique"
Exemple : type énuméré en langage C :

Cette fonctionnalité se traduit en langage C par le mot‐clé enum :

typedef enum { COEUR, CARREAU, TREFLE, PIQUE } couleur ;

char* nom_de_la_couleur (couleur c) {
	switch (c) {
		case COEUR   : return "♥ cœur" ;
		case CARREAU : return "♦ carreau" ;
		case TREFLE  : return "♣ trèfle" ;
		case PIQUE   : return "♠ pique" ;
	}
}

Type algébrique[modifier | modifier le code]

Un type algébrique est une somme de produits, et généralise donc ces deux notions.

Ainsi, des cas spéciaux de types algébriques sont les types produits (un seul constructeur), les types sommes (un seul argument pour chaque constructeur) et les types énumérations (plusieurs constructeurs sans argument).

Exemple : type option et type résultat :

Les types options (en) sont des applications courantes de types algébriques. Ils permettent d’ajouter à un type donné une valeur spéciale, considérée comme « indéfinie » ou comme valeur d’erreur (l’équivalent de null dans certains langages de programmation), ce qui permet de définir des fonctions partielles de façon contrôlée.

La valeur spéciale est représentée par un constructeur None qui ne prend aucun argument, tandis que les valeurs du type à compléter sont enveloppées dans un constructeur Some (qui prend donc un argument de ce type).

type int_option = None | Some of int

(* division : int -> int -> int_option *)
let division x y =
  if y = 0 then
    None
  else
    Some (x / y)

On peut perfectionner le mécanisme en agrémentant le cas d’erreur d’un message de description (donnée de type string).

type int_result = Result of int | Error of string

(* division : int -> int -> int_result *)
let division x y =
  if y = 0 then
    Error "division by zero"
  else
    Result (x / y)

Polymorphisme[modifier | modifier le code]

Dans les langages qui les supportent, les types algébriques peuvent être (paramétriquement) polymorphes, ce qui permet la programmation générique. Ainsi, la définition d’un type algébrique peut être paramétrée par des variables de types.

On peut alors définir des fonctions génériques agissant sur de tels types polymorphes.

Exemple : type option polymorphe :

On peut rendre polymorphe la définition du type option vue précédemment. Ça s’écrit ainsi en langage OCaml (où 'a dénote une variable de type) :

type 'a option = None | Some of 'a

(** Utilisation d’instances du type polymorphe : **)

(* int_division : int -> int -> int option *)
let int_division x y =
  if y = 0 then
    None
  else
    Some (x / y)

(* float_division : float -> float -> float option *)
let float_division x y =
  if y = 0.0 then
    None
  else
    Some (x /. y)

(** Définition d’une fonction générique : **)

(* get_value : 'a -> 'a option -> 'a *)
let get_value default_value optional_value =
  match optional_value with
  | None       -> default_value
  | Some value -> value

Type algébrique généralisé [modifier | modifier le code]

Récursivité[modifier | modifier le code]

Listes[modifier | modifier le code]

Un des exemples les plus importants de type algébrique est le type liste, défini de façon récursive par deux constructeurs :

  • Nil, aussi noté [], qui désigne la liste vide,
  • et Cons (abréviation de « constructeur »), aussi noté :: ou :, qui désigne la combinaison d’un élément et d’une liste plus courte.

Par exemple, Cons 1 (Cons 2 (Cons 3 (Cons 4 Nil))), aussi noté 1 :: 2 :: 3 :: 4 :: [], est la liste constituée des quatre éléments 1, 2, 3, 4, dans cet ordre.

Toutes les opérations sur les listes se définissent alors par récurrence, en utilisant le filtrage par motif. Par exemple, pour calculer la longueur d’une liste :

  • la longueur de la liste vide (Nil) est zéro,
  • et la longueur d’une liste de la forme Cons x suite est un plus la longueur de la liste suite.
Exemple : type liste en OCaml :

Cette définition se traduit ainsi en langage OCaml :

type 'a list =
  | Nil
  | Cons of 'a * 'a list

let list1234 = Cons 1 (Cons 2 (Cons 3 (Cons 4 Nil)))

let rec length = function
  | Nil      -> 0
  | Cons x s -> 1 + length s

Arbres[modifier | modifier le code]

Les types algébriques permettent également de définir des structures d’arbres. Un arbre binaire peut se construire au moyen de deux constructeurs :

  • Leaf e qui désigne une feuille d’étiquette e,
  • et Node e g d qui désigne un nœud interne d’étiquette e, de fils gauche g et de fils droit d.

Par exemple,

Node 1
  (Node 2
    (Leaf 4)
    (Node 5
      (Leaf 6)
      (Leaf 7)
    )
  )
  (Leaf 3)

est l’arbre suivant :

    1
   / \
  2   3
 / \
4   5
   / \
  6   7

Comme pour les listes, les opérations sur les arbres se définissent par récurrence. Par exemple, pour calculer la hauteur d’un arbre :

  • la hauteur d’une feuille est un,
  • et la hauteur d’un nœud interne est un plus le maximum de la hauteur de ses deux fils.
Exemple : type arbre binaire en OCaml :

Cette définition se traduit ainsi en langage OCaml :

type tree =
  | Leaf of int 
  | Node of tree * int * tree

let my_tree = Node 1 (Node 2 (Leaf 4) (Node 5 (Leaf 6) (Leaf 7))) (Leaf 3)

let rec height = function
  | Leaf e     -> 1
  | Node e l r -> 1 + max (height l) (height r)

Abstraction[modifier | modifier le code]

Un type algébrique peut être abstrait : il suffit pour ça de ne pas exposer sa structure interne (ses constructeurs et leurs divers champs). Ainsi, il ne peut être manipulé que par les fonctions prévues à cet effet, et son implémentation peut être changée.

C’est une technique fréquente car les types algébriques permettent de réaliser des structures de données complexes.

Voir aussi[modifier | modifier le code]

Notes & références[modifier | modifier le code]

Notes[modifier | modifier le code]

  1. C’est‐à‐dire un type formé en combinant d’autres types plus simples.
  2. De structurel, le typage devient alors nominal. Dans le premier cas, l’expression d’un n‐uplet permet de déduire entièrement sa structure (par exemple, ("clé", 37) est de type string * int) et déclarer le type est donc superflu. Dans le second cas, au contraire, l’expression ne suffit pas ({ key = "clé" ; value = 37 } peut suivre la structure { key : string ; value : int } mais aussi { value : int ; key : string } — qui est différente —, et l’expression entry.value permet seulement de déduire que la structure de entry contient un champ nommé value), et il faut donc déclarer les structures utilisées afin d’associer chaque nom de membre à une structure.

Références[modifier | modifier le code]

  • (en) Algebraic data type dans The Free On-line Dictionary of Computing, rédacteur en chef Denis Howe.