Parameterized Modules
Parameterized modules are to modules what functions are to base
values. Just like a function returns a new value from the values of
its parameters, a parameterized module builds a new module from the
modules given as parameters. Parameterized modules are also called
functors.
The addition of functors to the module language increases the
opportunities for code reuse in structures.
Functors are defined using a function-like syntax:
Syntax
functor ( Name : signature )
-> structure
# module
Couple
=
functor
(
Q
:
sig
type
t
end
)
->
struct
type
couple
=
Q.t
*
Q.t
end
;;
module Couple :
functor(Q : sig type t end) -> sig type couple = Q.t * Q.t end
As for functions, syntactic sugar is provided for defining and naming
a functor:
Syntax
module Name1 ( Name2 :
signature ) = structure
# module
Couple
(
Q
:
sig
type
t
end
)
=
struct
type
couple
=
Q.t
*
Q.t
end
;;
module Couple :
functor(Q : sig type t end) -> sig type couple = Q.t * Q.t end
A functor can take several parameters:
Syntax
functor ( Name1 : signature1 ) -> : |
functor ( Namen
: signaturen ) -> structure |
The syntactic sugar for defining and naming a functor extends to
multiple-argument functors:
Syntax
module Name (Name1 :
signature1 ) ...(
Namen : signaturen ) = |
structure |
The application of a functor to its arguments is written thus:
Syntax
module Name =
functor ( structure1 ) ...( structuren )
Note that each parameter is written between parentheses. The result
of the application can be either a simple module or a partially
applied functor, depending on the number of parameters of the functor.
Warning
There is no equivalent to functors at the level of signature: it is
not possible to build a signature by application of a ``functorial
signature'' to other signatures.
A closed functor is a functor that does not reference any module
except its parameters. Such a closed functor makes its communications
with other modules entirely explicit. This provides maximal
reusability, since the modules it references are determined at
application time only. There is a strong parallel between a closed
function (without free variables) and a closed functor.
Functors and Code Reuse
The Objective CAML standard library provides three modules defining
functors. Two of them take as argument a module implementing a
totally ordered data type, that is, a module with the following signature:
# module
type
OrderedType
=
sig
type
t
val
compare:
t
->
t
->
int
end
;;
module type OrderedType = sig type t val compare : t -> t -> int end
Function compare takes two arguments of type t
and returns a negative integer if the first is less than the second,
zero if both are equal, and a positive integer if the first is greater
than the second. Here is an example of totally ordered type: pairs of
integers equipped with lexicographic ordering.
# module
OrderedIntPair
=
struct
type
t
=
int
*
int
let
compare
(x1,
x2)
(y1,
y2)
=
if
x1
<
y1
then
-
1
else
if
x1
>
y1
then
1
else
if
x2
<
y2
then
-
1
else
if
x2
>
y2
then
1
else
0
end
;;
module OrderedIntPair :
sig type t = int * int val compare : 'a * 'b -> 'a * 'b -> int end
The functor Make from module Map returns a module
that implements association tables whose keys are values of the
ordered type passed as argument. This module provides operations
similar to the operations on association lists from module
List, but using a more efficient and more complex data
structure (balanced binary trees).
# module
AssocIntPair
=
Map.
Make
(OrderedIntPair)
;;
module AssocIntPair :
sig
type key = OrderedIntPair.t
and 'a t = 'a Map.Make(OrderedIntPair).t
val empty : 'a t
val add : key -> 'a -> 'a t -> 'a t
val find : key -> 'a t -> 'a
val remove : key -> 'a t -> 'a t
val mem : key -> 'a t -> bool
val iter : (key -> 'a -> unit) -> 'a t -> unit
val map : ('a -> 'b) -> 'a t -> 'b t
val fold : (key -> 'a -> 'b -> 'b) -> 'a t -> 'b -> 'b
end
The Make functor allows to construct association tables over
any key type for which we can write a compare function.
The standard library module Set also provides a functor named
Make taking an ordered type as argument and returning a
module implementing sets of sets of values of this type.
# module
SetIntPair
=
Set.
Make
(OrderedIntPair)
;;
module SetIntPair :
sig
type elt = OrderedIntPair.t
and t = Set.Make(OrderedIntPair).t
val empty : t
val is_empty : t -> bool
val mem : elt -> t -> bool
val add : elt -> t -> t
val singleton : elt -> t
val remove : elt -> t -> t
val union : t -> t -> t
val inter : t -> t -> t
val diff : t -> t -> t
val compare : t -> t -> int
val equal : t -> t -> bool
val subset : t -> t -> bool
val iter : (elt -> unit) -> t -> unit
val fold : (elt -> 'a -> 'a) -> t -> 'a -> 'a
val cardinal : t -> int
val elements : t -> elt list
val min_elt : t -> elt
val max_elt : t -> elt
val choose : t -> elt
end
The type SetIntPair.t is the type of sets of integer pairs,
with all the usual set operations provided in SetIntPair,
including a set comparison function SetIntPair.compare.
To illustrate the code reuse made possible by functors, we now build
sets of sets of integer pairs.
# module
SetofSet
=
Set.
Make
(SetIntPair)
;;
# let
x
=
SetIntPair.singleton
(1
,
2
)
;;
(* x = { (1,2) } *)
val x : SetIntPair.t = <abstr>
# let
y
=
SetofSet.singleton
SetIntPair.empty
;;
(* y = { {} } *)
val y : SetofSet.t = <abstr>
# let
z
=
SetofSet.add
x
y
;;
(* z = { {(1,2)} ; {} } *)
val z : SetofSet.t = <abstr>
The Make functor from module Hashtbl is similar to
that from the Map module, but implements (imperative) hash
tables instead of (purely functional) balanced trees. The argument to
Hashtbl.Make is slightly different: in addition to the type
of the keys for the hash table, it must provide an equality function
testing the equality of two keys (instead of a full-fledged comparison
function), plus a hash function, that is, a function associating
integers to keys.
# module
type
HashedType
=
sig
type
t
val
equal:
t
->
t
->
bool
val
hash:
t
->
int
end
;;
module type HashedType =
sig type t val equal : t -> t -> bool val hash : t -> int end
# module
IntMod13
=
struct
type
t
=
int
let
equal
=
(=
)
let
hash
x
=
x
mod
1
3
end
;;
module IntMod13 :
sig type t = int val equal : 'a -> 'a -> bool val hash : int -> int end
# module
TblInt
=
Hashtbl.
Make
(IntMod13)
;;
module TblInt :
sig
type key = IntMod13.t
and 'a t = 'a Hashtbl.Make(IntMod13).t
val create : int -> 'a t
val clear : 'a t -> unit
val add : 'a t -> key -> 'a -> unit
val remove : 'a t -> key -> unit
val find : 'a t -> key -> 'a
val find_all : 'a t -> key -> 'a list
val mem : 'a t -> key -> bool
val iter : (key -> 'a -> unit) -> 'a t -> unit
end
Local Module Definitions
The Objective CAML core language allows a module to be defined locally to an
expression.
Syntax
let module Name = structure |
in expr |
For instance, we can use the Set module locally to write a
sort function over integer lists, by inserting each list element into a
set and finally converting the set to the sorted list of its elements.
# let
sort
l
=
let
module
M
=
struct
type
t
=
int
let
compare
x
y
=
if
x
<
y
then
-
1
else
if
x
>
y
then
1
else
0
end
in
let
module
MSet
=
Set.
Make(M)
in
MSet.elements
(List.fold_right
MSet.add
l
MSet.empty)
;;
val sort : int list -> int list = <fun>
# sort
[
5
;
3
;
8
;
7
;
2
;
6
;
1
;
4
]
;;
- : int list = [1; 2; 3; 4; 5; 6; 7; 8]
Objective CAML does not allow a value to escape a let module
expression if the type of the value is not known outside the scope
of the expression.
# let
test
=
let
module
Foo
=
struct
type
t
let
id
x
=
(x:
t)
end
in
Foo.id
;;
Characters 15-101:
This `let module' expression has type Foo.t -> Foo.t
In this type, the locally bound module name Foo escapes its scope