ocaml modules allow to package together data structures definitions and functions operating on them. This allow to reduce code size and name confusion, as any identifier can appear in different modules, with different meanings, without any interference. This packaging is called structure, and is defined with the struct..end construction, that surrounds a sequence of definitions.
As an example, a module describing a priority queue can be like the following:
# module PrioQueue =
struct
type priority = int
type 'a queue = Empty | Node of priority * 'a * 'a queue * 'a queue
let empty = Empty
let rec insert queue prio elt =
match queue with
Empty -> Node(prio, elt, Empty, Empty)
| Node(p, e, left, right) ->
if prio <= p
then Node(prio, elt, insert right p e, left)
else Node(p, e, insert right prio elt, left)
exception Queue_is_empty
let rec remove_top = function
Empty -> raise Queue_is_empty
| Node(prio, elt, left, Empty) -> left
| Node(prio, elt, Empty, right) -> right
| Node(prio, elt, (Node(lprio, lelt, _, _) as left),
(Node(rprio, relt, _, _) as right)) ->
if lprio <= rprio
then Node(lprio, lelt, remove_top left, right)
else Node(rprio, relt, left, remove_top right)
let extract = function
Empty -> raise Queue_is_empty
| Node(prio, elt, _, _) as queue -> (prio, elt, remove_top queue)
end;;
To access a definition in the module, the usual dot-notation is used. As an example:
# PrioQueue.insert PrioQueue.empty 1 "hello";; - : string PrioQueue.queue = PrioQueue.Node (1, "hello", PrioQueue.Empty, PrioQueue.Empty)
In ocaml, the idea of module signature can be found. The signature is built out of some definitions of the ones present in the module definition, those that will be seen outside the module itself, i.e. that will be exported.
As an example, PrioQueue (see above) could have a signature exporting empty, insert and extract, but not remove_top.
# module type PRIOQUEUE =
sig
type priority = int (* still concrete *)
type 'a queue (* now abstract *)
val empty : 'a queue
val insert : 'a queue -> int -> 'a -> 'a queue
val extract : 'a queue -> int * 'a * 'a queue
exception Queue_is_empty
end;;
Functors are functions from structures to structures. They are useful to have some parameters in the definition of a structure: a structure A with a parameter B behaves as a functor F with a formal parameter B, that returns the actual structure A.
As an example, a structure implementing an ordered set, having as a parameter a structure that exports the data type of set elements and a comparing function, could be:
# type comparison = Less | Equal | Greater;;
# module type ORDERED_TYPE =
sig
type t
val cmp: t -> t -> comparison
end;;
# module Set =
functor (Elt: ORDERED_TYPE) ->
struct
type element = Elt.t
type set = element list
let empty = []
let rec add x s =
match s with
[] -> [x]
| hd::tl ->
match Elt.cmp x hd with
Equal -> s (* x is already in s *)
| Less -> x :: s (* x is smaller than all elements of s *)
| Greater -> hd :: add x tl
let rec member x s =
match s with
[] -> false
| hd::tl ->
match Elt.cmp x hd with
Equal -> true (* x belongs to s *)
| Less -> false (* x is smaller than all elements of s *)
| Greater -> member x tl
end;;
To create the actual structure, ocaml uses a syntax like the following:
# module OrderedString = (* ordered set of strings *)
struct
type t = string
let cmp x y = if x = y then Equal else if x < y then Less else Greater
end;;
# module StringSet = Set(OrderedString);; (* creazione del modulo *)