Programmation Avancée

TP3: Sous-typage et abstraction

Préliminaire: types fantômes

Exercices conçu par David Baelde

L'idée générale des types fantômes est d'attacher à un type abstrait un paramètre de type qui serait inutile du point de vue du type concret associé, ceci afin d'obtenir une API agréable et (plus) sure.

Cette idée n'est pas propre à OCaml. Le wiki Haskell illustre cette technique avec l'exemple (vu en cours) de sanitisation de chaines de caratères. Voici ce que cela donne en OCaml. D'abord la signature:

type 'a str
type clean
type dirty

(** On peut calculer la longueur de toute chaîne. *)
val length : 'a str -> int
(** Les inputs sont sales: on se méfie de l'utilisateur. *)
val read : unit -> dirty str
(** Il faut les nettoyer ... *)
val sanitize : dirty str -> clean str
(** ... avant de pouvoir les afficher. *)
val write : clean str -> unit

puis l'implémentation, très simple et sans surcoût à l'exécution:

type 'a str = string
type clean = unit (* ou autre, qu'importe *)
type dirty = unit

let length s = String.length s
let read () = read_line ()
let write s = Printf.printf "%s\n" s
let sanitize s = Filename.quote s

Avec cette API on ne peut exécuter write (read ()). On est forcés de faire write (sanitize (read ())) ce qui évite d'afficher des caractères spéciaux sans échappement.

A l'intérieur du module défini ci-dessus, on notera que le type 'a str est covariant et contravariant en son paramètre 'a, puisque celui-ci est inutilisé. La signature de ce module rend le type abstrait: en l'absence d'information, le type 'a t y est invariant en son paramètre. On peut le rendre covariant en annotant, dans la signature, type +'a str (ou contravariant avec -'a); cela ne serait bien sûr pas permis si la définition concrète de str dans l'implémentation n'était pas covariante (et contravariante pour l'annotation -'a).

Exercice: permissions fantômes

Dans l'exemple préliminaire, on avait des fonctions qui ne prennent qu'un type de chaine, et des fonctions qui les prennent tous. Nous allons voir dans l'exercice qui suit comment exploiter le sous-typage pour faire des distinctions plus fines.

Question 1

On considère un type de descripteur de fichier, annoté par les permissions que l'on a sur le fichier: rw pour lecture+écriture, ro pour lecture seule et wo pour écriture seule. Nous souhaitons interdire, par typage, l'écriture sur un fichier en lecture seule, et vice versa.

Récupérer la base de code ici, et , le Makefile, ainsi que les différents fichiers de no tests, le fichier test ainsi que les fichiers strings Quand vous tapez make, cela compile le module File ainsi que quelques tests d'utilisation. Les fichiers test<i>.ml sont censés compiler, mais pas les fichiers notest<i>.ml. Si quelquechose ne se passe pas comme prévu, make échoue en affichant ERREUR; c'est le cas dans l'archive initiale.

Votre mission est de mettre en place une discipline de type qui empêche les mauvais bouts de code de compiler, en modifiant uniquement file.mli et file.ml: il est interdit d'insérer ne serait-ce qu'une coercion dans les autres fichiers.

Vous utiliserez un type fantôme, dont le paramètre sera instancié par différents types d'objets en fonction des permissions associées au fichier. Clairement, il vous faudra utiliser du sous-typage sur le paramètre fantôme.