• Aucun résultat trouvé

4.3 Le langage de modules

4.3.2 Foncteurs

Il faut remarquer que les param`etres des d´eclarations de types abstraits doivent ˆetre annot´es par leurs sortes et variances dans les signatures : en l’absence de repr´esentation concr`ete du type, ils ne peuvent plus ˆetre inf´er´es par le syst`eme, mais leur connaissance est toutefois n´ecessaire au typage du reste du programme.

4.3.2 Foncteurs

Les foncteurs sont des«fonctions»des structures vers les structures qui permettent d’exprimer des structures param´etr´ees. Un exemple courant est la d´efinition d’une librairie impl´ementant des ensembles, param´etr´ee par une structure donnant le type des ´el´ements et une fonction d’ordre total sur ces derniers.

Comme il est usuel en Caml,compare x yest suppos´ee retourner0sixetysont ´egaux, un entier n´egatif si xest strictement inf´erieur `a y et un entier positif sinon. Dans cette signature, le type des ´el´ements, t, est param´etr´e par un seul niveau d’information, qui d´ecrit toute l’information potentiellement obtenue lors d’une comparaison, comme le refl`ete le type decompare. Cependant, cela n’empˆeche aucunement cette signature d’ˆetre utilis´ee avec des structures de donn´ees complexes dont le type porte plusieurs niveaux d’information :

4.3·Le langage de modules 75

Dans ce module, le param`etre du type t repr´esente l’union des deux annotations port´ees par le type des listes d’entiers. Cela permet `a la fonctioncomparequi suit d’acc´eder `a la fois `a la structure des listes et `a leurs ´el´ements pour les comparer.

Je d´efinis maintenant le foncteur qui r´ealise des ensembles sur des ´el´ements de type arbitraire. Ce foncteur prend une structureEltcomme argument, laquelle doit avoir la signatureORDERED_TYPE:

module Set : functor (Elt : ORDERED_TYPE) -> sig type (#’a:level) element = ’a Elt.t

Comme dans l’exemple deIntSet, un bon style de programmation consiste `a cacher l’impl´emen-tation concr`ete du type des ensembles. Cela peut ˆetre effectu´e en restreignant le type du foncteur Set. D´efinissons tout d’abord la signature de la structure produite par le foncteur :

Le type du foncteurSet peut ˆetre restreint lors de sa d´efinition, en annotant son en-tˆete comme suit :

Set (Elt: ORDERED_TYPE)

76 Chapitre 4·D´efinitions de types et de modules

: (SET ’a element = ’a Elt.t) = ...

La contrainte de type ’a element = ’a Elt.ta le mˆeme rˆole qu’en Objective Caml : elle raffine la signatureSETde mani`ere `a exprimer le fait que les ensembles contiennent des ´el´ements de type ’a Elt.t. Pour conclure avec cet exemple, observons qu’il est possible d’obtenir une nouvelle impl´ementation des ensembles d’entiers, comme une instance du foncteur Set, qui a la mˆeme signature que celle obtenue de mani`ere directe :

Dans les exemples pr´ec´edents, l’interaction entre le langage de module et l’analyse de flots mise en œuvre par le syst`eme de type de Flow Caml est relativement simple. Elle n´ecessite essentiellement de fournir les annotations utiles dans les d´eclarations de valeurs et de types des signatures. Le typage de certaines structures et foncteurs n´ecessite toutefois d’introduire des d´eclarations de niveaux abstraits, introduites par le mot-clef , qui ont un rˆole comparable `a celles des d´eclarations de types. Par exemple, on peut d´efinir un type de module pour des structures impl´ementant un canal de communication ouvert en lecture comme suit :

read : unit -{Prompt ||}-> Data string

;;

Cette signature fait intervenir deux niveaux d’information abstraits :Dataest le niveau des donn´ees lues sur le canal ; etPromptrepr´esente l’information potentiellement transmise sur le canal lorsqu’un processus effectue une lecture sur ce dernier. Dans cette signature, ces niveaux sont totalement inconnus : on dit qu’ils restentabstraits. Ils sont toutefois utilis´es dans le type de la fonction read , qui est suppos´ee lire une ligne de texte sur le canal sous-jacent : cette fonction a la possibilit´e d’effectuer un effet de niveauPromptet retourne une chaˆıne de niveauData. Remarquons que, avec ce type pourread, la lecture sur le canal est suppos´ee ne jamais ´echouer. Voici une impl´ementation de cette signature pour l’entr´ee standard :

val read : unit -{[< !stdout, !stdin] ||}-> !stdin string end

Les chaˆınes lues sur l’entr´ee standard ont le niveau !stdinauquelDataest d´eclar´e ´egal. De plus, une invocation de read_lineaffecte `a la fois l’entr´ee standard et la sortie standard (puisque le

4.3·Le langage de modules 77

cache de cette derni`ere est vid´e avant la lecture), de telle sorte que le niveau Prompt doit ˆetre inf´erieur ou ´egal `a!stdinet !stdout. Ainsi, le moduleStdinimpl´emente la signatureIN, ce qui peut ˆetre imm´ediatement v´erifi´e par une contrainte de type :

AbstractStdin = (Stdin : IN);;

module AbstractStdin : IN

De la mˆeme mani`ere, je peux d´eclarer un type de module pour les canaux ouverts en ´ecriture, et en d´efinir une instance avec la sortie standard :

OUT =

Data

print : Data string -{Data ||}-> unit

;;

val print : !stdout string -{!stdout ||}-> unit end;;

Dans ce cas, un seul niveau est n´ecessaire,Data, qui repr´esente l’information qui peut ˆetre envoy´ee sur le canal. (Je ne consid`ere pas la possibilit´e derecevoir de l’information d’un canal ouvert en

´ecriture, par exemplevia sa saturation.)

Je cherche maintenant `a ´ecrire un foncteur param´etr´e par deux structures impl´ementant res-pectivement un canal d’entr´ee et un canal de sortie. Le corps de ce foncteur d´efinira une fonction copyqui permet simplement de lire une ligne sur le canal ouvert en lecture et de la recopier sur celui ouvert en ´ecriture. De ce fait, il n’est pas suffisant de d´eclarer les deux param`etres du foncteur comme ´etant de signatures respectivesINetOUT: en effet, la fonctioncopyg´en`ere un flot d’infor-mation entre les deux canaux. Le niveau d’inford’infor-mation Datadu premier doit donc ˆetre inf´erieur ou ´egal `a celui du second :

val print : Data string -{Data ||}-> unit end) ->

sig

val copy : unit -{[< O.Data, I.Prompt] ||}-> unit end

Cela est r´ealis´e ci-dessus grˆace `a la contrainte qui apparaˆıt dans le type du second param`etre du foncteur. Son effet est similaire `a celle des et d’Objective Caml : elle raffine la d´efinition du niveau Datadans la signature du module O. Il faut noter que l’ordre dans lequel apparaissent les deux param`etres du foncteur introduit une certaine asym´etrie, puisque la contrainte est appliqu´e au second. Il est naturellement possible de permuter I et O comme suit :

Copier’ (O : OUT)

(I : IN Data O.Data) =

78 Chapitre 4·D´efinitions de types et de modules

val read : unit -{Prompt ||}-> Data string end) ->

sig

val copy : unit -{[< I.Prompt, O.Data] ||}-> unit end

Pour terminer cet exemple, d´efinissons une instance deCopierop´erant sur l’entr´ee standard et la sortie standard. Il suffit pour cela d’entrer la d´efinition suivante :

val print : !stdout string -{!stdout ||}-> unit end

is not included in sig

level Data greater than Stdin.Data

val print : Data string -{Data ||}-> unit end

Level declarations mismatch: the provided type declaration level Data = !stdout

is not included in the expected one level Data greater than Stdin.Data

The inequality Stdin.Data < Data is required but not provided

En effet, en l’´etat actuel des choses, le module Stdout ne satisfait pas la signature OUT

Data Stdin.Datapuisque le niveauStdout.Data, qui est ´egal au principal

!stdout n’est pas sup´erieur ou ´egal `a Stdin.Data qui vaut !stdin. En d’autres termes, il est n´ecessaire d’avoir l’in´egalit´e!stdin < !stdoutpuisque la fonctioncopyfourni par cette instance deCopierengendre un flot d’information de l’entr´ee standard vers la sortie standard. Pour autoriser cela, il suffit de relaxer la politique de s´ecurit´e comme suit :

!stdin < !stdout;;

D´esormais,Stdin.Dataest inf´erieur `aStdout.Data, de telle sorte queStdinetStdout sont des arguments l´egaux pour le foncteurCopier:

StdCopier = Copier (Stdin) (Stdout);;

module StdCopier :

sig val copy : unit -{[< Stdout.Data, Stdin.Prompt] ||}-> unit end