Author: Christian.Queinnec@lip6.fr

## The Module Language

The Objective CAML language features a sub-language for modules, which comes in addition to the core language that we have seen so far. In this module language, the interface of a module is called a signature and its implementation is called a structure. When there is no ambiguity, we will often use the word ``module'' to refer to a structure.
The syntax for declaring signatures and structures is as follows:

### Syntax

module type NAME =
sig
interface declarations
end

### Syntax

module Name =
struct
implementation definitions
end

### Warning

The name of a module must start with an uppercase letter. There are no such case restrictions on names of signatures, but by convention we will use names in uppercase for signatures.

Signatures and structures do not need to be bound to names: we can also use anonymous signature and structure expressions, writing simply

### Syntax

sig declarations end

### Syntax

struct definitions end

We write signature and structure to refer to either names of signatures and structures, or anonymous signature and structure expressions.
Every structure possesses a default signature, computed by the type inference system, which reveals all the definitions contained in the structure, with their most general types. When defining a structure, we can also indicate the desired signature by adding a signature constraint (similar to the type constraints from the core language), using one of the following two syntactic forms:

### Syntax

module Name : signature = structure

### Syntax

module Name = (structure : signature)

When an explicit signature is provided, the system checks that all the components declared in the signature are defined in the structure structure, and that the types are consistent. In other terms, the system checks that the explicit signature provided is ``included in'', or implied by, the default signature. If so, Name is viewed in the remainder of the code with the signature ``signature'', and only the components declared in the signature are accessible to the clients of the module. (This is the same behavior we saw previously with interface files.)
Access to the components of a module is via the dot notation:

### Syntax

Name1.name2

We say that the name name2 is qualified by the name Name1 of its defining module.
The module name and the dot can be omitted using a directive to open the module:

### Syntax

open Name

In the scope of this directive, we can use short names name2 to refer to the components of the module Name. In case of name conflicts, opening a module hides previously defined entities with the same names, as in the case of identifier redefinitions.

### Two Stack Modules

We continue the example of stacks by recasting it in the module language. The signature for a stack module is obtained by wrapping the declarations from the `stack.mli` file in a signature declaration:

    # 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

A first implementation of stacks is obtained by reusing the `Stack` module from the standard library:

    # module StandardStack = Stack ;;
    module StandardStack :
      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

We then define an alternate implementation based on arrays:

    # module MyStack =
       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 increase s x =  s.c <- Array.append s.c (Array.create 5 x)
         let push x s =
           if s.sp >= Array.length s.c then increase s x;
           s.c.(s.sp) <- x;
           s.sp <- succ s.sp
         let pop s =
           if s.sp =0 then raise Empty
           else (s.sp <- pred s.sp ; s.c.(s.sp))
         let length s = s.sp
         let iter f s = for i = pred s.sp downto 0 do f s.c.(i) done
       end ;;
    module MyStack :
      sig
        type 'a t = { mutable sp: int; mutable c: 'a array }
        exception Empty
        val create : unit -> 'a t
        val clear : 'a t -> unit
        val increase : 'a t -> 'a -> 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

These two modules implement the type `t` of stacks by different data types.

    # StandardStack.create () ;;
    - : '_a StandardStack.t = <abstr>
    # MyStack.create () ;;
    - : '_a MyStack.t = {MyStack.sp=0; MyStack.c=[||]}

To abstract over the type representation in `Mystack`, we add a signature constraint by the `STACK` signature.

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

The two modules `StandardStack` and `MyStack` implement the same interface, that is, provide the same set of operations over stacks, but their `t` types are different. It is therefore impossible to apply operations from one module to values from the other module:

    # let s = StandardStack.create() ;;
    val s : '_a StandardStack.t = <abstr>
    # MyStack.push 0 s ;;
    Characters 15-16:
    This expression has type 'a StandardStack.t = 'a Stack.t
    but is here used with type int MyStack.t

Even if both modules implemented the t type by the same concrete type, constraining `MyStack` by the signature `STACK` suffices to abstract over the t type, rendering it incompatible with any other type in the system and preventing sharing of values and operations between the various stack modules.

    # module S1 = ( MyStack : STACK ) ;;
    module S1 : STACK
    # module S2 = ( MyStack : 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

The Objective CAML system compares abstract types by names. Here, the two types `S1.t` and `S2.t` are both abstract, and have different names, hence they are considered as incompatible. It is precisely this restriction that makes type abstraction effective, by preventing any access to the definition of the type being abstracted.

### Modules and Information Hiding

This section shows additional examples of signature constraints hiding or abstracting definitions of structure components.

#### Hiding Type Implementations

Abstracting over a type ensures that the only way to construct values of this type is via the functions exported from its definition module. This can be used to restrict the values that can belong to this type. In the following example, we implement an abstract type of integers which, by construction, can never take the value 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

#### Hiding Values

We now define a symbol generator, similar to that of page ??, using a signature constraint to hide the state of the generator.
We first define the signature `GENSYM` exporting only two functions for generating symbols.

    # module type GENSYM =
       sig
         val reset : unit -> unit
         val next : string -> string
       end ;;

We then implement this signature as follows:

    # 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

The reference `c` holding the state of the generator `Gensym` is not accessible outside the two exported functions.

    # 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

The definition of `c` is essentially local to the structure `Gensym`, since it is hidden by the associated signature. The signature constraint achieves more simply the same goal as the local definition of a reference in the definition of the two functions `reset_s` and `new_s` on page ??.

#### Multiple Views of a Module

The module language and its signature constraints support taking several views of a given structure. For instance, we can have a ``super-user interface'' for the module `Gensym`, allowing the symbol counter to be reset, and a ``normal user interface'' that permits only the generation of new symbols, but no other intervention on the counter. To implement the latter interface, it suffices to declare the signature:

    # module type USER_GENSYM =
       sig
         val next : string -> string
       end;;
    module type USER_GENSYM = sig val next : string -> string end

We then implement it by a mere signature constraint.

    # module UserGensym = (Gensym : USER_GENSYM) ;;
    module UserGensym : USER_GENSYM
    # UserGensym.next "U" ;;
    - : string = "U2"
    # UserGensym.reset() ;;
    Characters 0-16:
    Unbound value UserGensym.reset

The `UserGensym` module fully reuses the code of the `Gensym` module. In addition, both modules share the same counter:

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

### Type Sharing between Modules

As we saw on page ??, abstract types with different names are incompatible. This can be problematic when we wish to share an abstract type between several modules. There are two ways to achieve this sharing: one is via a special sharing construct in the module language; the other one uses the lexical scoping of modules.

#### Sharing via Constraints

The following example illustrates the sharing issue. We define a module `M` providing an abstract type M.t. We then restrict `M` on two different signatures exporting different subsets of operations.

    # 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
       :
       sig
         type t
         val create : unit -> t
         val add : t -> unit
         val get : t -> 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

As written above, the types M1.t and M2.t are incompatible. However, we would like to say that both types are abstract but identical. To do this, Objective CAML offers special syntax to declare a type equality over an abstract type in a signature.

### Syntax

NAME with type t1 = t2 and ...

This type constraint forces the type t1 declared in the signature NAME to be equal to the type t2.
Type constraints over all types exported by a sub-module can be declared in one operation with the syntax

### Syntax

NAME with module Name1 = Name2

Using these type sharing constraints, we can declare that the two modules `M1` and `M2` define identical abstract types.

    # 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

#### Sharing and Nested Modules

Another possibility for ensuring type sharing is to use nested modules. We define two sub-modules (`M1` et `M2`) sharing an abstract type defined in the enclosing module `M`.

    # 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

As desired, values created by `M1` can be operated upon by `M2`, while hiding the representation of these values.

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

This solution is heavier than the previous solution based on type sharing constraints: the functions from `M1` and `M2` can only be accessed via the enclosing module `M`.

### Extending Simple Modules

Modules are closed entities, defined once and for all. In particular, once an abstract type is defined using the module language, it is impossible to add further operations on the abstract type that depend on the type representation without modifying the module definition itself. (Operations derived from existing operations can of course be added later, outside the module.) As an extreme example, if the module exports no creation function, clients of the module will never be able to create values of the abstract type!
Therefore, adding new operations that depend on the type representation requires editing the sources of the module and adding the desired operations in its signature and structure. Of course, we then get a different module, and clients need to be recompiled. However, if the modifications performed on the module signature did not affect the components of the original signature, the remainder of the program remains correct and does not need to be modified, just recompiled.