Bonjour,
Lors de notre dernière leçon, nous avons posé les premières pierres de votre apprentissage d'OCaml en explorant la syntaxe de base, la récursivité avec let rec, et surtout le filtrage par motif (pattern matching) sur les listes. Cette dernière compétence est absolument centrale, et nous allons aujourd'hui l'étendre bien au-delà des listes.
L'objectif de cette leçon est de vous apprendre à modéliser des structures de données complexes en définissant vos propres types. En OCaml, on utilise pour cela les types algébriques de données (TAD), qui se composent principalement de deux sortes de types : les types produits (comme les records et les tuples) et les types sommes (les variants). La capacité à définir des types précis pour représenter un problème est une compétence fondamentale en informatique, et sa maîtrise est un atout majeur pour l'Agrégation.
1. Les types produits : Avoir "ceci" ET "cela"
Commençons par le cas où une donnée est un agrégat de plusieurs autres. Par exemple, un étudiant a un nom et une année de diplôme. Un point dans un plan a une abscisse et une ordonnée. On parle de types produits, car ils correspondent mathématiquement au produit cartésien des types de leurs composantes.
OCaml nous offre deux manières principales de construire ces types : les records (enregistrements) et les tuples (n-uplets).
Les enregistrements (records)
Un enregistrement regroupe plusieurs valeurs en leur donnant un nom (un champ). C'est l'équivalent des struct en C ou des objets simples en Python. Ils sont particulièrement utiles lorsque les champs ont une sémantique claire.
La vidéo suivante vous montrera la syntaxe pour définir un type record, créer une valeur de ce type, et accéder à ses champs.
Records and Tuples | OCaml Programming | Chapter 3 Video 3
Cette vidéo de Michael Ryan Clarkson explique de manière pratique comment utiliser les enregistrements en OCaml.
Regardez la première partie de la vidéo (jusqu'à 04:05). Concentrez-vous sur la syntaxe de la définition de type (type student = { ... }), la création d'une valeur (let rbg = { ... }), et l'accès aux champs avec la notation . (par exemple, rbg.name).
La syntaxe est donc type nom_du_type = { champ1: type1; champ2: type2; ... }.
Les n-uplets (tuples)
Un n-uplet est une structure plus légère qui agrège un nombre fixe de valeurs sans nommer les champs. On y accède par position.
Reprenez la vidéo précédente pour voir comment ils fonctionnent.
Records and Tuples | OCaml Programming | Chapter 3 Video 3
La suite de la vidéo présente les n-uplets comme une alternative aux enregistrements.
Regardez la seconde partie de la vidéo (de 04:05 à 07:30). Notez la syntaxe pour définir et créer un n-uplet, comme (10, 10, "am"). Observez que pour les paires (tuples à 2 éléments), on peut utiliser les fonctions fst et snd pour accéder aux composantes. Pour les n-uplets plus longs, on utilise généralement le filtrage par motif, comme nous le verrons.
Alors, quand utiliser l'un ou l'autre ?

Pour formaliser l'idée de "type produit", regardez ce court extrait.
Algebraic Data Types | OCaml Programming | Chapter 3 Video 17
Cette vidéo de Michael Ryan Clarkson donne une explication conceptuelle des types produits, en les reliant aux produits cartésiens.
Regardez la section de 02:22 à 03:14. L'idée clé est que les enregistrements et les tuples sont des types "chacun de" (each of), car une valeur de ce type possède chacune des composantes.
2. Les types sommes : Avoir "ceci" OU "cela"
Passons maintenant au cas où une donnée peut prendre l'une de plusieurs formes possibles. Par exemple, une forme géométrique est un cercle ou un rectangle. Un moyen de paiement est une carte de crédit ou un virement ou du liquide. Ces types sont appelés types sommes ou variants.
C'est là que la véritable puissance du système de types d'OCaml et du filtrage par motif commence à se manifester. Un type variant est défini par une liste de constructeurs.
Cet article de 'Real World OCaml' est une excellente ressource sur les variants. Nous allons commencer par les bases.
Lisez la première partie de la section 'Variants', jusqu'à la définition du type color. Observez la syntaxe type basic_color = Black | Red | ... et comment le filtrage par motif est utilisé pour exploiter les valeurs de ce type. C'est une application directe de ce que vous avez vu dans la leçon précédente.
Un variant peut faire plus que simplement énumérer des constantes. Chaque constructeur peut également "transporter" des données. C'est ce qui nous permet de modéliser des structures complexes.
Le document en français suivant, issu d'un cours de l'École Polytechnique, présente cela sous le nom de "types construits".
Ce document vous montrera comment définir des types variants (appelés 'types construits') qui portent des données, et comment les utiliser avec le filtrage par motif. L'exemple est très pertinent car il définit un type récursif.
Lisez attentivement la section 1.4.5 'Types construits'. Concentrez-vous sur l'exemple type formule = .... Notez comment le constructeur Conjonction prend deux arguments de type formule. Analysez ensuite la fonction evalue qui utilise le filtrage par motif pour déconstruire et évaluer une formule.
Conceptuellement, ces types correspondent à une union disjointe (ou "union étiquetée" - tagged union) en mathématiques.
Algebraic Data Types | OCaml Programming | Chapter 3 Video 17
Pour conclure cette section, retournons à la vidéo conceptuelle pour formaliser la notion de type somme.
Regardez la section de 03:14 à 05:41. L'idée clé est que les variants sont des types "l'un de" (one of), car une valeur de ce type est exactement l'une des possibilités offertes par les constructeurs.
3. La puissance des types algébriques : combiner sommes et produits
La véritable expressivité vient de la capacité à combiner et imbriquer types sommes et types produits. Un exemple canonique, fondamental en informatique, est l'arbre binaire.
Comment modéliser un arbre binaire en OCaml ?
- Un arbre est soit vide, soit un nœud. (-> type somme / variant)
- Un nœud contient une valeur, et un sous-arbre gauche, et un sous-arbre droit. (-> type produit / tuple ou record)
Cela se traduit directement en OCaml :

Cette seule définition est une synthèse parfaite de tout ce que nous avons vu : variants, produits (ici un tuple implicite) et récursivité.
Pour aller plus loin et voir comment ces concepts sont utilisés dans un contexte de conception logicielle plus vaste, la lecture suivante est très instructive.
Cet article de 'Real World OCaml' va plus loin et montre des techniques de conception avancées en utilisant les types algébriques. C'est une lecture qui vous prépare à des questions de conception plus profondes, typiques de l'Agrégation.
Lisez les sections 'Combining Records and Variants' et 'Variants and Recursive Data Structures'. La première section montre comment restructurer des types pour améliorer la clarté du code. La seconde présente un autre exemple classique de type récursif pour un langage d'expressions, très similaire à l'exercice qui suit.
4. Exercice de synthèse
Pour consolider ces notions, vous allez modéliser et évaluer des expressions arithmétiques simples.
Énoncé :
-
Définissez un type
exprpour représenter une expression arithmétique. Une expression peut être :- Une constante entière.
- L'addition de deux expressions.
- La soustraction de deux expressions.
- La multiplication de deux expressions.
Indice : vous aurez besoin d'un constructeur pour chaque cas. Trois d'entre eux porteront des données.
-
Écrivez une fonction récursive
evalue : expr -> intqui prend une expression et retourne le résultat de son évaluation. Vous devrez utiliser le filtrage par motif sur votre typeexpr.
Prenez le temps nécessaire pour réfléchir à la structure du type, puis à celle de la fonction.
Solution
(* 1. Définition du type algébrique pour les expressions *)
type expr =
| Const of int
| Add of expr * expr
| Sub of expr * expr
| Mul of expr * expr
(* 2. Fonction d'évaluation récursive *)
let rec evalue e =
match e with
| Const n -> n (* Cas de base : la valeur d'une constante est elle-même *)
| Add (e1, e2) -> (evalue e1) + (evalue e2) (* Cas récursifs *)
| Sub (e1, e2) -> (evalue e1) - (evalue e2)
| Mul (e1, e2) -> (evalue e1) * (evalue e2)
(* Quelques tests pour vérifier *)
let () =
(* Crée l'expression (3 * (5 + 2)) *)
let my_expr = Mul (Const 3, Add (Const 5, Const 2)) in
assert (evalue my_expr = 21);
(* Crée l'expression (10 - 4) *)
let another_expr = Sub (Const 10, Const 4) in
assert (evalue another_expr = 6);
print_endline "Tests passés avec succès !"
Conclusion
Cette leçon vous a doté d'un outil d'une puissance considérable : la capacité de créer vos propres types de données pour modéliser précisément un problème. C'est le cœur de la programmation en OCaml.
Points clés à retenir :
- Les types produits (
records,tuples) servent à agréger des données qui doivent toutes être présentes (une relation ET). Lesrecordssont préférables lorsque les champs ont une sémantique claire. - Les types sommes (
variants) servent à représenter un choix parmi plusieurs formes possibles (une relation OU). - La combinaison des deux, souvent de manière récursive, permet de créer des types algébriques de données (TAD) très expressifs, comme les arbres ou les langages formels.
- Le filtrage par motif est le mécanisme idiomatique et sûr pour manipuler les valeurs de ces types, en garantissant que tous les cas sont traités.
Dans notre prochaine leçon, nous explorerons un autre pilier de la programmation fonctionnelle : les fonctions d'ordre supérieur. Nous verrons comment écrire des fonctions qui prennent d'autres fonctions en argument, comme map, filter et fold, ce qui nous permettra d'écrire du code encore plus général et réutilisable.