Author: Christian.Queinnec@lip6.fr

## Modules simples

Le langage Objective CAML est distribué avec un certain nombre de modules déjà définis. Nous avons vu leur utilisation au chapitre 8. Nous allons voir, dans ce paragraphe, comment réaliser de tels modules.

### Interface et implantation

La distribution d'Objective CAML fournit le module `Stack` implantant les fonctions principales pour utiliser une pile (LIFO).

    # let queue = Stack.create () ;;
    val queue : '_a Stack.t = <abstr>
    # Stack.push 1 queue ; Stack.push 2 queue ; Stack.push 3 queue ;;
    - : unit = ()
    # Stack.iter (fun n -> Printf.printf "%d " n)  queue ;;
    3 2 1 - : unit = ()

Objective CAML étant distribué avec ses sources, nous pouvons regarder comment les piles ont été implantées.
`ocaml-2.04/stdlib/stack.ml``
`type`` 'a`` t`` `=``` {`` mutable`` c`` `:``` 'a`` list`` }
exception`` Empty
let`` create`` ()`` `=``` {`` c`` `=``` []`` }
let`` clear`` s`` `=``` s`.`c`` `<-``` []
let`` push`` x`` s`` `=``` s`.`c`` `<-``` x`` ::`` s`.`c
let`` pop`` s`` `=``` match`` s`.`c`` with`` `` hd::tl`` ->`` s`.`c`` `<-``` tl;`` hd`` `` `|``` `` []`` `` ->`` raise`` Empty
let`` length`` s`` `=``` List.length`` s`.`c
let`` iter`` f`` s`` `=``` List.iter`` f`` s`.`c
Nous nous apercevons que le type des piles connu sous le nom de `Stack.t` est un enregistrement constitué d'un champ modifiable contenant une liste. Les opérations sur la pile sont réalisées par les opérations classiques sur les listes appliquées à l'unique champ de l'enregistrement.
Fort de cette connaissance, rien ne nous interdit a priori d'accéder directement à la liste constituant la queue; cela n'est en fait pas le cas.

    # let liste = queue.c ;;
    Characters 13-18:
    This expression has type int Stack.t but is here used with type 'a vm

Le compilateur proteste comme s'il ne connaissait pas l'identificateur du champ du type `Stack.t`. C'est en fait le cas, comme nous nous en apercevons en regardant l'interface du module `Stack`.
`ocaml-2.04/stdlib/stack.mli``
``(* Module [Stack]: last-in first-out stacks *)`
`(* This module implements stacks (LIFOs), with in-place modification. *)`
type`` 'a`` t`` `` `` `` `` `` `` `` `` `(* The type of stacks containing elements of type ['a]. *)`
exception`` Empty`` `` `` `(* Raised when [pop] is applied to an empty stack. *)`
val`` create`:``` unit`` ->`` 'a`` t
`` `` `` `` `` `` `` `` `(* Return a new stack, initially empty. *)`
val`` push`:``` 'a`` ->`` 'a`` t`` ->`` unit
`` `` `` `` `` `` `` `` `(* [push x s] adds the element [x] at the top of stack [s]. *)`
val`` pop`:``` 'a`` t`` ->`` 'a
`` `` `` `` `` `` `` `` `(* [pop s] removes and returns the topmost element in stack [s],`
`or raises [Empty] if the stack is empty. *)`
val`` clear`` `:``` 'a`` t`` ->`` unit
`` `` `` `` `` `` `` `` `(* Discard all elements from a stack. *)`
val`` length`:``` 'a`` t`` ->`` int
`` `` `` `` `` `` `` `` `(* Return the number of elements in a stack. *)`
val`` iter`:``` `(`'a`` ->`` unit`)``` ->`` 'a`` t`` ->`` unit
`` `` `` `` `` `` `` `` `(* [iter f s] applies [f] in turn to all elements of [s],`
`from the element at the top of the stack to the element at the`
`bottom of the stack. The stack itself is unchanged. *)`
Outre des commentaires expliquant comment utiliser les fonctions du module `Stack`, ce fichier explicite quels sont les valeurs, les types et les exceptions définis dans le fichier `stack.ml`. Plus précisément, l'interface fournit les noms des valeurs et leur type. En particulier, le nom du type `t` est donné, mais son implantation (c'est-à-dire l'identificateur du champ : `c`) n'est pas fournie. Ceci explique que l'utilisateur du module `Stack` ne puisse accéder directement à ce champ. Le type Stack.t est dit abstrait.
Les fonctions de manipulation des piles sont elles aussi simplement déclarées sans être définies. Il faut seulement, pour que le mécanisme d'inférence de types puisse juger de leur utilisation légitime, indiquer le type de chacune d'elles. Ceci est fait grâce à un nouveau mot clé :

### Syntaxe

val nom : type

### Relation entre interface et implantation

Ainsi le module `Stack` est en fait constitué de deux entités : une interface et une implantation. Pour construire un module, il faut que tous les éléments déclarés dans l'interface soient effectivement définis dans l'implantation. Il est également nécessaire que les définitions des fonctions satisfassent la déclaration de type donnée dans l'interface1.
La relation entre interface et implantation n'est pas symétrique. L'implantation peut très bien contenir plus de définitions que n'en demande l'interface. Typiquement, la définition d'une fonction un peu complexe pourra utiliser des fonctions auxiliaires dont le nom n'apparaît pas dans l'interface. Dans ce cas, le programmeur utilisant un tel module, tout comme il ne peut faire référence au champ `c` de la structure `Stack.t`, ne peut faire appel à la fonction auxiliaire non déclarée dans l'interface. De même il est possible dans l'interface d'apporter des restrictions sur le type des valeurs. Imaginons qu'un module définisse la fonction d'identité (let`` id`` x`` `=``` x) mais que son interface déclare la valeur `id` de type int -> nt; alors les modules utilisant la valeur `id` ne pourront le faire que sur des entiers.
La dualité de constitution d'un module permet de rendre la déclaration des entités composant un module indépendante de leur implantation. Ainsi, on peut substituer au fichier d'implantation `stack.ml` de la distribution, un autre fichier, que l'on appelle toujours `stack.ml` et qui contient une implantation différente des piles reposant, par exemple, sur des tableaux :

    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 = Array.length s.c
    let iter f s = Array.iter f s.c

Cet ensemble de fonctions satisfait le requisit du fichier d'interface `stack.mli`. Le nouveau couple de fichiers `stack.mli` et `stack.ml` (nouvelle mouture) fournit une alternative au module `Stack` de la distribution d'Objective CAML.

### Compilation séparée

À l'instar des autres langages de programmation modernes, Objective CAML permet de découper un programme en plusieurs unités de compilation. Une unité de compilation est constituée de deux fichiers, le fichier d'implantation (d'extension `.ml`) et le fichier d'interface (d'extension `.mli`). Chaque unité de compilation est vue comme un module. Ainsi la compilation du fichier `nom.ml` produit le module `Nom`2. Les valeurs, types et exceptions définis dans un module peuvent être référencés soit en utilisant la notation pointée (`Module`.`identificateur`), soit en utilisant le mot clé open.

`nom1.ml` | `nom2.ml`
type`` t`` `=``` {`` x`:`int`` ;`` y`:`int`` }`` ;; | let`` val`` `=``` {`` Nom1.x`` `=``` `1``` ;`` Nom1.y`` `=``` `2``` }`` ;;
let`` f`` c`` `=``` c`.`x`` `+``` c`.`y`` ;; | Nom1.f`` val`` ;;
| open`` Nom1`` ;;
| f`` val`` ;;

Le fichier d'interface, portant l'extension `.mli`, doit être compilé en utilisant la commande `ocamlc -c` avant la compilation des modules qui en dépendent dont en particulier le fichier d'implantation de ce même module. En cas d'absence d'un fichier d'interface, Objective CAML considère que la totalité du module est exportée; c'est-à-dire que toutes les déclarations de l'implantation sont présentes dans l'interface implicite avec leur type le plus général.
L'édition de liens pour engendrer l'exécutable se fait comme décrit au chapitre 7 avec la commande `ocamlc` (sans l'option `-c`) suivi des fichiers objets. Attention, sur la ligne de commande les fichiers doivent être dans l'ordre de leur dépendance; ceci interdit donc les dépendances croisées entre modules.
Pour engendrer un exécutable à partir des fichiers `nom1.ml` et `nom2.ml`, nous utiliserons les commandes suivantes :

    > ocamlc -c nom1.ml
    > ocamlc -c nom2.ml
    > ocamlc nom1.cmo nom2.cmo

Cette façon de procéder par fichier d'interface et fichier d'implantation pour constituer les modules permet la compilation séparée, mais les possibilités de structuration logique qu'elle offre sont faibles. En particulier, le nom des modules est implicite. Il est déduit du nom des fichiers constituant le module. L'amalgame entre module et fichier empêche d'avoir en même temps deux implantations d'une même interface, voire deux interfaces pour une même implantation. Pour pallier ce défaut, le mécanisme de module a été intégré à la syntaxe du langage de programmation. C'est ce que nous présentons dans la suite de ce chapitre.