------------------------

Harvey Mudd College
Computer Science 131
Programming Languages
Spring Semester 2000

Lecture 08

------------------------

------------------------

Functors which provide the glue that makes the module system truely useful.

Functors allow a structure definition to be given as a template, and instantiated to some appropriate parameters for an actual application.

Suppose we have the following signature for a lookup table:

(* A signature for string dictionaries *)
 
signature DICTIONARY =
sig
   eqtype key
   type def
   type dict
    
   exception Already_Defined of key;
   exception Not_Defined of key;
 
   val newdict : dict;
   val lookup  : dict -> key -> def option
   val defined : dict -> key -> bool
   val define  : dict -> key -> def -> dict
   val undefine: dict -> key -> dict
end;

One structure matching this signature is:

(* A structure for string-string dictionaries *)
 
structure StringStringDictionary : DICTIONARY = 
struct
   type key = string
   type def = string
   datatype dict = dict of (key * def) list
 
   exception Not_Defined of key;
   exception Already_Defined of key;
 
   val newdict = dict nil;
 
   fun lookup_aux nil _ = NONE
     | lookup_aux ((key1,def1)::t) key = 
          if key1 = key
            then SOME def1
            else lookup_aux t key1
 
   fun lookup (dict dct) key = lookup_aux dct key
 
   fun defined dct key = not (lookup dct key = NONE)
 
   fun define (dict' as (dict dct)) key def = 
          if (defined dict' key)
            then raise (Already_Defined key)
            else dict ((key,def)::dct)
 
   fun undefine_aux nil key = raise (Not_Defined key)
     | undefine_aux (dct as ((defn as (key1,def1))::t)) key =
          if key1 = key
            then t
            else defn::(undefine_aux t key)
 
   fun undefine (dict dct) key = dict (undefine_aux dct key)
end;

We could similarly define structures for any key/definition types as long as the key type is an equality type. Notice, however, that this very definition should work for all those cases. It would be silly to have to repeat it over an over. Can we just leave out the key and definition types and let the functions be polymorphic? No. The problem is that such a structure would not match the signature we were given.

The solution is to use a functor:

(* A functor that builds dictionary structures *)
 
functor Dictionary (eqtype K; type D) : DICTIONARY = 
struct
   type key = K
   type def = D
   datatype dict = dict of (key * def) list
 
   exception Not_Defined of key
   exception Already_Defined of key
 
   val newdict = dict nil
 
   fun lookup_aux nil _ = NONE
     | lookup_aux ((key1,def1)::t) key = 
          if key1 = key
            then SOME def1
            else lookup_aux t key1
 
   fun lookup (dict dct) key = lookup_aux dct key
 
   fun defined dct key = case (lookup dct key) 
                            of NONE   => false
                             | SOME _ => true
 
   fun define (dict' as (dict dct)) key def = 
          if (defined dict' key)
            then raise (Already_Defined key)
            else dict ((key,def)::dct)
 
   fun undefine_aux nil key = raise (Not_Defined key)
     | undefine_aux (dct as ((defn as (key1,def1))::t)) key =
          if key1 = key
            then t
            else defn::(undefine_aux t key)
 
   fun undefine (dict dct) key = dict (undefine_aux dct key)
end;

If we wish to use this functor to build a structure for dictionaries with integer keys and string definitions, we type:

structure IntStringDict = Dictionary(type K = int; 
                                    type D = string);

Note that I changed the definition of defined slightly. Why?

------------------------

A functor can be parameterized by a type, a value, or a structure. As an example of the last, consider that if it is possible to build dictionaries that are more efficient if we know that the key type supports an ordering function. The functor now needs to recieve the key and definition types as well as the ordering function for the keys. While these could be passed in separately, it makes sense to group the function with the key type. We will use the following signature for this bundle:

(* A signature for string dictionaries *)
 
signature ORDERING =
sig
   eqtype T
 
   val lt : T * T -> bool
end;

Now, we can build a functor for ordered dictionaries as:

(* A functor that builds sorted dictionary structures *)
 
functor SortedListDictionary (structure Ordering : ORDERING; type D) : DICTIONARY = 
struct
   type key = Ordering.T
   type def = D
   datatype dict = dict of (key * def) list
 
   exception Not_Defined of key
   exception Already_Defined of key
 
   val newdict = dict nil
 
   fun lookup_aux nil _ = NONE
     | lookup_aux ((key1,def1)::t) key = 
          if key1 = key
            then SOME def1
            else if Ordering.lt (key1,key)
                   then lookup_aux t key1
                   else NONE;
 
   fun lookup (dict dct) key = lookup_aux dct key
 
   fun defined dct key = case (lookup dct key) 
                            of NONE   => false
                             | SOME _ => true
 
   fun define_aux nil key def = [(key,def)]
     | define_aux (dct as ((defn as(key1,def1))::t)) key def = 
          if key1 = key
            then raise (Already_Defined key)
            else if Ordering.lt(key1,key)
                   then defn::(define_aux t key def)
                   else (key,def)::dct
 
   fun define (dict dct) key def = dict (define_aux dct key def)
 
   fun undefine_aux nil key = raise (Not_Defined key)
     | undefine_aux (dct as ((defn as(key1,def1))::t)) key =
          if key1 = key
            then t
            else if Ordering.lt(key1,key)
                   then defn::(undefine_aux t key)
                   else raise (Not_Defined key)
 
   fun undefine (dict dct) key = dict (undefine_aux dct key)
end;

Then, If we have the ORDERING:

(* A structure for the integer ordering *)
 
structure IntOrder : ORDERING = 
struct
   type T = int
 
   val lt = Int.<
end;

we can use this functor to build a structure for fast dictionaries with integer keys and string definitions, by typing:

structure SortedIntStringDict =
              SortedListDictionary(structure Ordering = IntOrder; 
                                   type D = string);

------------------------

Suppose we are writing a functor that makes use of a DICTIONARY and also made use of a QUEUE, which signature includes a queue type and an entry type. At some point in the code an element is taken off the queue and looked up in the dictionary. The functor would look something like:

functor SomeFunctor (structure Dict : DICTIONARY  and Queue : QUEUE) : SOME_SIG = 
struct
   ...

   fun lookUpHead dict queue = Dict.lookup dict (Queue.head queue);

   ...
end

But does (or should) this typecheck?

The types of the two functions are:

val Dict.lookup : Dict.dict -> Dict.key -> Dict.def; 
val Queue.head : Queue.queue -> Queue.entry;

So, it all depends on the particular dictionary and queue that is sent in to the functor. If Dict.key is the same type as Queue.entry then we are fine. Otherwise, not.

We can tell the system that they will be the same, and thus that it should, on application of the functor, check that they are the same, by adding a sharing constraint to the functor header. In particular, the functor becomes:

functor SomeFunctor (structure Dict : DICTIONARY  and Queue : QUEUE
                     sharing type Dict.key = Queue.entry) : SOME_SIG =
struct
   ...

   fun lookUpHead dict queue = Dict.lookup dict (Queue.head queue);

   ...
end

Here we have enforced a sharing constraint on types. In a similar manner, it is also sometimes necessary to require that two structures share some common substructure. In that case we just use a structure sharing constraint as in:

functor Foo (structure Struct1 : SIG1 and Struct2 : SIG2
             sharing structure Struct1.sub1 = Struct2.sub2) : FOO_SIG =
struct

   ...

end

------------------------

This page copyright ©2000 by Joshua S. Hodas. It was built on a Macintosh. Last rebuilt on Monday, January 31, 2000.
http://www.cs.hmc.edu/~hodas/courses/cs131/lectures/lecture08s.html