informatique (MP2I/MPI) · Chapitre 6 · Application directe du cours
Problème — Un module OCaml compilé : ce que la signature refuse
Types et structures de données abstraites · niveau 3 (difficile)
Énoncé
- Écrire un module
ensemble.mli/ensemble.mlréalisant un ensemble d'entiers par liste triée sans doublon. - Le compiler avec
ocamlcet l'utiliser. - Montrer que la signature interdit de fabriquer un ensemble qui violerait l'invariant.
- Que se passe-t-il si l'on supprime le fichier
.mli?
Corrigé
1. Le module.
(* ensemble.mli -- l'INTERFACE. Le type n'a pas de definition ici. *)
type t
val vide : t
val ajouter : int -> t -> t
val appartient : int -> t -> bool
val cardinal : t -> int
(* ensemble.ml -- la REALISATION.
INVARIANT : la liste est triee par ordre strictement croissant. *)
type t = int list
let vide = []
let rec ajouter x = function
| [] -> [x]
| y :: r when x < y -> x :: y :: r
| y :: r when x = y -> y :: r (* deja present : rien a faire *)
| y :: r -> y :: ajouter x r
let rec appartient x = function
| [] -> false
| y :: _ when x = y -> true
| y :: _ when x < y -> false (* triee : on peut s'arreter *)
| _ :: r -> appartient x r
let cardinal = List.length
2. La compilation et l'usage.
ocamlc ensemble.mli ensemble.ml usage.ml -o usage
(* usage.ml *)
let e = Ensemble.ajouter 3 (Ensemble.ajouter 1 (Ensemble.ajouter 3 Ensemble.vide))
let () = Printf.printf "cardinal = %d, 1 present : %b, 2 present : %b\n"
(Ensemble.cardinal e) (Ensemble.appartient 1 e) (Ensemble.appartient 2 e)
Mesuré : cardinal = 2, 1 present : true, 2 present : false. L'ajout répété de n'a produit qu'un élément : l'invariant « sans doublon » est tenu par ajouter, et lui seul en est responsable.
3. Ce que la signature refuse. On tente de fabriquer un ensemble à la main, ni trié ni sans doublon :
let e : Ensemble.t = [2; 1; 1]
Error: This constructor has type 'a list
but an expression was expected of type Ensemble.t
Le compilateur ne sait plus que Ensemble.t est une liste — la signature le lui a caché. Toute valeur du type est donc sortie de vide ou de ajouter, et l'invariant tient par récurrence sur la construction : c'est une preuve, pas une convention.
4. Sans le .mli. On compile les deux mêmes fichiers, sans l'interface :
ocamlc -c ensemble.ml && ocamlc -c fraude.ml --> les deux compilent
La ligne let e : Ensemble.t = [2; 1; 1] passe. En l'absence de .mli, OCaml expose la signature complète, définition du type comprise : Ensemble.t est int list, et l'invariant n'est plus protégé par rien. appartient 1 rendrait alors false sur [2;1;1], puisqu'elle s'arrête au premier élément supérieur.
Le fichier .mli n'est donc pas de la documentation, et c'est l'enseignement du problème : c'est lui qui transforme un commentaire d'invariant en une propriété vérifiée par le compilateur. Le typedef struct pile_s pile; de C joue exactement le même rôle, avec la même conséquence — et c'est en cela que les deux langages, si différents par ailleurs, réalisent la même idée de structure abstraite.