For the past few classes we have been considering abstraction and
modular design, primarily through the use of the `module`

mechanism in OCaml. We have seen that good design principles include
writing clear specifications of interfaces, independent of the actual
implementation. We have also seen that writing good documentation of
the implementation is important. Today we will consider another means
of abstraction called *functors*, that enable modules to be
combined together by parameterizing a module in terms of other
modules.

Consider the set data abstraction that we have looked at during the past few classes:

module type SETSIG = sig type 'a set val empty : 'a set val add : 'a -> 'a set -> 'a set val mem : 'a -> 'a set -> bool val rem : 'a -> 'a set -> 'a set val size: 'a set -> int val union: 'a set -> 'a set -> 'a set val inter: 'a set -> 'a set -> 'a set end

While this interface uses polymorphism to enable sets with
different types of elements to be created, any implementation of this
signature needs to use the built-in `=`

function in testing
whether an element is a member of such a set. Thus we cannot for
example have a set of strings where comparison of the elements is done
in a case-insensitive manner, or a set of integers where elements are
equal when their magnitudes are equal (i.e., their absolute values are
equal). We could write two separate signatures, one for sets with
string elements and one for sets with integer elements, and then in
the implementation of each signature use an appropriate comparison
function. However this would yield a lot of nearly duplicated code,
both in the signatures and in the implementation. Such nearly
duplicated code is more work to write and maintain and more
importantly is often a source of bugs when things are changed in one
place and not another.

A *functor* is a module that is parameterized by other
modules. Functors will allow us to create a set module that is
parameterized by another module that does equality testing, thereby
allowing the same code to be used for different equality tests. To
make this concrete we will consider an example with the following simple
interface for sets:

module type SETSIG = sig type set type elt val empty : set val mem : elt -> set -> bool val add: elt -> set -> set val find: elt -> set -> elt end

Note that this interface differs from the one above, in that it
defines a set of a given fixed element type, rather than an ```
'a
set
```

. It also defines just three operations, although others
could easily be added.

In addition to a set abstraction, we also need another module that abstracts the comparison operation on the elements of the set, which will be used to parameterize the set module. The signature for this type comparator is simply a type and a comparison function:

module type EQUALSIG = sig type t val equal : t -> t -> bool end

Now we are ready to define a functor implementing
the `SETSIG`

signature. Unlike other module
implementations we have seen, this module will not be instantiated
directly, but rather will be used to define modules that are
instantiated. Thus it is an abstract "template" that defines set
modules in terms of equality testing modules with
the `EQUALSIG`

signature. Here is the definition,
understanding it will take a bit of discussion:

module MakeSet (Equal : EQUALSIG) : SETSIG with type elt = Equal.t = struct open Equal type elt = t type set = elt list let empty = [] let mem x s = List.exists (equal x) s let add x s = if mem x s then s else x :: s let find x s = List.find (equal x) s end

First note that before the specification of the
signature `SETSIG`

being implemented here, there is the
expression `(Equal : EQUALSIG)`

. This means that the
module `MakeSet`

is parameterized by a module with
signature `EQUALSIG`

and this module will be referred to
using the name `Equal`

in the body of the module
definition. In general there can be any number of modules
parameterizing a module, each of which must be specified in
parentheses with a name that will be used in the body together with
the type signature of the module. Note that these parameters to a
module can only be modules (including functors), they cannot be
first-class objects of the language such as functions or other types.

Further note that after the specification of
the `SETSIG`

signature is the expression

with type elt = Equal.t =

This expression specifies that these two types are shared. When
combining different modules together using functors, often types in
one module must be the same as types in the other module. In the
current example the `elt`

type of the `SETSIG`

signature must be the same as the `t`

type of
the `EQUALSIG`

signature. In general there can be any
number of such sharing constraints between types.

The body of the `MakeSet`

module is like the body of any
other module. In this example the `open`

directive is used
so that the names `t`

and `equal`

can be
referred to without qualifying them as `Equal.t`

and `Equal.equal`

.

It is also worth noting the partial uncurrying
of `equal`

for instance in:

let mem x s = List.exists (equal x) s

which returns a function that tests whether a given element is
equal to the value of `x`

, and that function is used
by `List.exists`

.

Note that the `MakeSet`

module (and any functor) is
abstract. We use the functor to create a module, and then use that
resulting module. The `MakeSet`

module itself does not
define operations like a standard module. For instance there is
no `MakeSet.add`

, but there will be an `add`

operation in whatever modules are created using `MakeSet`

.

In order to use `MakeSet`

we need an implementation of a
module with the `EQUALSIG`

signature. Here is such an
implementation for testing equality of strings in a case independent
fashion:

module StringNoCase = struct type t = string let equal s1 s2 = String.lowercase s1 = String.lowercase s2 end

Now we can use `MakeSet`

to create a string set module
with case insenstitive equality:

module SSet = MakeSet (StringNoCase)

Evaluating this expression the interpreter prints out:

module SSet : sig type set = MakeSet(StringNoCase).set type elt = StringNoCase.t val empty : set val mem : elt -> set -> bool val add : elt -> set -> set val find : elt -> set -> elt end

That is, the `SSet`

module defines the
types `set`

and ```
elt and the
function
```

`mem`

, `add`

, and `find`

.

```
```Now we can use this set abstraction to create and manipulate sets
of strings, with case insensitive comparison of elements in a set.

# let s = SSet.add "I like CS 3110" SSet.empty;;
val s : SSet.set =
# SSet.mem "i LiKe cs 3110" s;;
- : bool = true
# SSet.find "i LiKe cs 3110" s;;
- : SSet.elt = "I like CS 3110"

Now creating a module for sets of integers using absolute value
comparison involves almost no additional code. All that is necessary
is to create a module with the `EQUALSIG`

signature and
then use that as the parameter to `MakeSet`

:

module IntAbs = struct
type t = int
let equal i1 i2 =
(abs i1) = (abs i2)
end
module ISet = MakeSet (IntAbs)

Now we can use this set abstraction to create and manipulate sets
of integers, with absolute value comparison of elements in a set:

# let i = ISet.add 1 ISet.empty;;
val i : ISet.set =
# ISet.mem (-1) i;;
- : bool = true
# ISet.find (-1) i;;
- : ISet.elt = 1