Précédent Index Suivant

Langage des modules simples

Le langage Objective CAML possède un sous-langage de modules venant s'ajouter noyau du langage. Dans ce cadre, l'interface d'un module est appelée sa signature et son implantation est désignée comme le module si il n'y a pas d'ambiguïté.

Syntaxe


module type MSIG =
    sig
        interface
    end



Syntaxe


module MStruct [ : MSIG ] =
    struct
        implantation
    end

Warning


Le nom d'un module doit impérativement commencer par une majuscule. Celui d'une signature est libre, mais conventionnellement on utilise des noms en majuscules.

Warning


Dans le code d'une signature ou celui d'un module, les différentes déclarations ne doivent pas se terminer par un double point-virgule.



Toute structure a par défaut une signature calculée par l'inférence de type qui reprend l'intégralité des définitions contenues dans la structure. On peut lors de la définition d'une structure, préciser quelle est la signature attendue en rajoutant le coercion optionnelle [ : MSIG ] . Dans ce cas, le système vérifie que tout ce qui est déclaré dans MSIG est défini dans MStruct et que les types sont cohérents. En d'autres termes, la signature attendue MSIG est incluse dans la signature par défaut. Si tel est le cas, MStruct devient un module de signature MSIG et, de façon analogue à ce qui se passait avec les fichiers d'interface, seuls les objets déclarés par la signature sont accessibles à l'utilisateur du module.

On peut aussi utiliser le nom d'une structure pour créer un nouveau module ayant une signature prédéfinie. On a alors recours à la syntaxe suivante :

Syntaxe


module M = ( MStruct : MSIG)


L'accès aux entités déclarées d'un module se fait en utilisant la notation pointée :

Syntaxe


M.nom
On dit alors que le nom nom est qualifié.

On peut rendre implicite le nom du module en utilisant la directive d'ouverture des modules :

Syntaxe


open M
Dès lors, on peut utiliser les noms des entités sans les qualifier. L'ouverture d'un module provoque, en cas d'identité de nom, le masquage des entités préalablement définies, à la façon des redéfinitions d'identificateurs.

Deux modules pour les piles

Reprenons les piles en utilisant le langage des modules. Nous commençons par définir la signature d'une pile en reprenant les déclarations du fichier stack.mli :

# module type STACK =
sig
type 'a t
exception Empty
val create: unit -> 'a t
val push: 'a -> 'a t -> unit
val pop: 'a t -> 'a
val clear : 'a t -> unit
val length: 'a t -> int
val iter: ('a -> unit) -> 'a t -> unit
end ;;
module type STACK =
sig
type 'a t
exception Empty
val create : unit -> 'a t
val push : 'a -> 'a t -> unit
val pop : 'a t -> 'a
val clear : 'a t -> unit
val length : 'a t -> int
val iter : ('a -> unit) -> 'a t -> unit
end

On obtient une première implantation des piles en utilisant celui de la bibliothèque standard :

# module Stack_distrib = Stack ;;
module Stack_distrib :
sig
type 'a t = 'a Stack.t
exception Empty
val create : unit -> 'a t
val push : 'a -> 'a t -> unit
val pop : 'a t -> 'a
val clear : 'a t -> unit
val length : 'a t -> int
val iter : ('a -> unit) -> 'a t -> unit
end

On en définit une seconde utilisant des tableaux :

# module Stack_perso =
struct
type 'a t = { mutable sp : int; mutable c : 'a array }
exception Empty
let create () = { sp=0 ; c = [||] }
let clear s = s.sp <- 0; s.c <- [||]
let size = 5
let increase s = s.c <- Array.append s.c (Array.create size s.c.(0))
let push x s =
if s.c = [||] then ( s.c <- Array.create size x; s.sp <- succ s.sp )
else ( (if s.sp = Array.length s.c then increase s) ;
s.c.(s.sp) <- x ;
s.sp <- succ s.sp )
let pop s = if s.sp =0 then raise Empty
else let x = s.c.(s.sp) in s.sp <- pred s.sp ; x
let length s = s.sp
let iter f s = for i=0 to pred s.sp do f s.c.(i) done
end ;;
module Stack_perso :
sig
type 'a t = { mutable sp: int; mutable c: 'a array }
exception Empty
val create : unit -> 'a t
val clear : 'a t -> unit
val size : int
val increase : 'a t -> unit
val push : 'a -> 'a t -> unit
val pop : 'a t -> 'a
val length : 'a t -> int
val iter : ('a -> 'b) -> 'a t -> unit
end


Les deux modules implantent chacune un type t qui n'est pas le même.

# Stack_distrib.create () ;;
- : '_a Stack_distrib.t = <abstr>
# Stack_perso.create () ;;
- : '_a Stack_perso.t = {Stack_perso.sp=0; Stack_perso.c=[||]}
On retrouve l'abstraction de type en forçant la signature du second module.

# module Stack_perso = (Stack_perso : STACK) ;;
module Stack_perso : STACK
# Stack_perso.create() ;;
- : '_a Stack_perso.t = <abstr>


Les deux modules Stack_perso et Stack_distrib n'ont en commun que le nom des fonctions qu'ils implantent. Par contre, leur type sont différents; il n'est donc pas possible d'utiliser les fonctions de l'un pour manipuler les valeurs de l'autre :

# let s = Stack_distrib.create() ;;
val s : '_a Stack_distrib.t = <abstr>
# Stack_perso.push 0 s ;;
Characters 19-20:
This expression has type 'a Stack_distrib.t = 'a Stack.t
but is here used with type int Stack_perso.t
Même si les deux modules avaient possédés un type t de même implantation, le fait d'abstraire ce type en coerçant le module avec la signature STACK interdit la possibilité de partager les valeurs entre les deux modules.

# module S1 = ( Stack_perso : STACK ) ;;
module S1 : STACK
# module S2 = ( Stack_perso : STACK ) ;;
module S2 : STACK
# let s = S1.create () ;;
val s : '_a S1.t = <abstr>
# S2.push 0 s ;;
Characters 10-11:
This expression has type 'a S1.t but is here used with type int S2.t
Objective CAML ne dispose pour vérifier la compatibilité des types que de leur nom (leur implantation étant abstraite) et ici ils sont différents : S1.t et S2.t.

Modules et portée lexicale

Nous donnons dans ce paragraphe deux exemples d'utilisation de signatures pour masquer certaines déclarations.

Masquage de types

Abstraire un type permet de restreindre les valeurs qu'il est possible de construire. Dans l'exemple suivant, nous obtenons des entiers dont la construction nous assure qu'ils sont obligatoirement différents de 0.

 
# module Int_Star =
( struct
type t = int
exception Isnul
let of_int = function 0 -> raise Isnul | n -> n
let mult = (+)
end
:
sig
type t
exception Isnul
val of_int : int -> t
val mult : t -> t -> t
end
) ;;
module Int_Star :
sig type t exception Isnul val of_int : int -> t val mult : t -> t -> t end


Masquage de valeurs

Le masquage d'une valeur permet de réaliser un générateur de symbole analogue à celui vu page . On définit la signature GENSYM contenant seulement deux déclarations de fonctions pour la génération de symboles.

# module type GENSYM =
sig
val reset : unit -> unit
val next : string -> string
end ;;
On implante ensuite une structure cohérente pour une telle signature :

# module Gensym : GENSYM =
struct
let c = ref 0
let reset () = c:=0
let next s = incr c ; s ^ (string_of_int !c)
end;;
module Gensym : GENSYM
La référence globale c de la structure Gensym n'est pas accessible en dehors des deux fonctions exportées.

# Gensym.reset();;
- : unit = ()
# Gensym.next "T";;
- : string = "T1"
# Gensym.next "X";;
- : string = "X2"
# Gensym.reset();;
- : unit = ()
# Gensym.next "U";;
- : string = "U1"
# Gensym.c;;
Characters 0-8:
Unbound value Gensym.c
La déclaration de c peut être considérée comme locale à la stucture module Gensym puisqu'elle est masquée par la signature associée au module. Cela permet de simplifier le partage d'environnement entre différentes fermetures. À l'intérieur d'un module, on met tout au même niveau et c'est l'attribution de la signature qui détermine e qui sera global (ie : accessible) et ce qui sera local (ie : masqué).

Différentes vues d'un même module

Le langage de module avec coercion de signature permet d'offir plusieurs vues d'une même structure. On pourra, par exemple avoir un << super-utilisateur >> du module Gensym qui est capable de remettre à jour le compteur et un utilisateur ordinaire qui ne peut que créer un nouveau symbole sans maîtriser le compteur. Pour obtenir ce dernier, il suffit de poser la signature :

# module type USER_GENSYM =
sig
val next : string -> string
end;;
On crée ensuite le module correspondant par la déclaration :

# module UserGensym = (Gensym : USER_GENSYM) ;;
module UserGensym : USER_GENSYM
# UserGensym.next "U" ;;
- : string = "U2"
# UserGensym.reset() ;;
Characters 0-16:
Unbound value UserGensym.reset
Pour réaliser ce nouveau module on a réutilisé le module Gensym. De plus, les deux modules partagent le même compteur :

# Gensym.next "U" ;;
- : string = "U3"
# Gensym.reset() ;;
- : unit = ()
# UserGensym.next "V" ;;
- : string = "V1"


Partage de types entre modules

L'incompatibilité entre types abstraits signalée un peu avant (page X) pose problème lorsque l'on désire partager un type abstrait entre plusieurs modules. Nous examinons les façons de procéder dans ce paragraphe. L'une est une construction explicite du langage de modules, l'autre utilise la structure de bloc lexical des modules.

Type manifeste

Illustrons le problème à l'aide du petit exemple suivant. On définit un module et nous le restreignons suivant deux signatures différentes.

# module M =
struct
type t = int ref
let create() = ref 0
let add x = incr x
let get x = if !x>0 then (decr x; 1) else failwith"Empty"
end ;;
module M :
sig
type t = int ref
val create : unit -> int ref
val add : int ref -> unit
val get : int ref -> int
end
#
module type S1 =
sig
type t
val create : unit -> t
val add : t -> unit
end ;;

# module type S2 =
sig
type t
val get : t -> int
end ;;
#
module M1 = (M:S1) ;;
module M1 : S1
# module M2 = (M:S2) ;;
module M2 : S2

Puisque les deux modules ont des types abstraits, il est impossible de les identifier.

# let x = M1.create () in M1.add x ; M2.get x ;;
Characters 45-46:
This expression has type M1.t but is here used with type M2.t


Pour obtenir cette identification, Objective CAML dispose d'une syntaxe pour ``ouvrir'' un type normalement abstrait dans une signature.

Syntaxe


MSIG with   type t1 = t2 and ...
  with   module M1 = M2 and ...)



En utilisant cette contrainte de partage, on peut déclarer les deux modules M1 et M2 comme manipulant la même structure de données.

# module M1 = (M:S1 with type t = M.t) ;;
module M1 : sig type t = M.t val create : unit -> t val add : t -> unit end
# module M2 = (M:S2 with type t = M.t) ;;
module M2 : sig type t = M.t val get : t -> int end
# let x = M1.create() in M1.add x ; M2.get x ;;
- : int = 1


Partage et sous-modules

Le défaut de la solution précédente est qu'en fin de compte le type partagé n'est pas abstrait. En définissant deux sous-modules partageant un type de donné abstrait d'un module englobant, nous pouvons parvenir au résultat souhaité.

# module M =
( struct
type t = int ref
module M_hide =
struct
let create() = ref 0
let add x = incr x
let get x = if !x>0 then (decr x; 1) else failwith"Empty"
end
module M1 = M_hide
module M2 = M_hide
end
:
sig
type t
module M1 : sig val create : unit -> t val add : t -> unit end
module M2 : sig val get : t -> int end
end ) ;;
module M :
sig
type t
module M1 : sig val create : unit -> t val add : t -> unit end
module M2 : sig val get : t -> int end
end


On obtient bien le résultat voulu : une valeur créée par M1 peut être manipulée par M2 et pourtant cette valeur est abstraite.

# let x = M.M1.create() ;;
val x : M.t = <abstr>
# M.M1.add x ; M.M2.get x ;;
- : int = 1


Modules simples et extension

Un module est une entité définie une fois pour toutes. En particulier, lorsque nous définissons un type abstrait à l'aide du mécanisme de modules nous ne pouvons plus en étendre les traitements. En particulier, si il n'a pas été défini de fonction de création, on ne pourra jamais obtenir de valeur de ce type !

Une façon brutale d'augmenter les traitements fournis par un module est d'éditer les sources et de rajouter ce que l'on désire dans la signature et la structure. Mais alors, on n'a plus du tout affaire au mêmes modules et toutes les applications qui utilisaient la version originale du module sont à recompiler. Notons cependant que si la redéfinition des composants du module n'a pas modifié les éléments de l'interface originale, il suffit uniquement de recompiler l'ensemble de l'application sans avoir à modifier ce qui avait était écrit.


Précédent Index Suivant