The syntax for declaring signatures and structures is as follows:

module type NAME = |

sig |

interface declarations |

end |

module Name = |

struct |

implementation definitions |

end |

The name of a module

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

We write

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:

When an explicit signature is provided, the system checks that all the components declared in the signature are defined in the structure

Access to the components of a module is via the dot notation:

We say that the name

The module name and the dot can be omitted using a directive to open the module:

In the scope of this directive, we can use short names

`stack.mli`

file in a signature
declaration:
A first implementation of stacks is obtained by reusing the

#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`

#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

# StandardStack.create`()`

`;;`

`- : '_a StandardStack.t = <abstr>`

# MyStack.create`()`

`;;`

`- : '_a MyStack.t = {MyStack.sp=0; MyStack.c=[||]}`

To abstract over the type representation in

#module`MyStack`

`=`

(MyStack

`:`

`STACK`

)`;;`

`module MyStack : STACK`

# MyStack.create()`;;`

`- : '_a MyStack.t = <abstr>`

The two modules

#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

#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

#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`

We first define the signature

#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

# 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

#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

# Gensym.next

`"U"`

`;;`

`- : string = "U3"`

# Gensym.reset()`;;`

`- : unit = ()`

# UserGensym.next

`"V"`

`;;`

`- : string = "V1"`

#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

This type constraint forces the type

Type constraints over all types exported by a sub-module can be declared in one operation with the syntax

Using these type sharing constraints, we can declare that the two modules

#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`

#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

This solution is heavier than the previous solution based on type sharing constraints: the functions from

#let`x`

`=`

`M`

`.`

M1.create()`;;`

`val x : M.t = <abstr>`

# M`.`

M1.add`x`

`;`

`M`

`.`

M2.get`x`

`;;`

`- : int = 1`

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.