Calculating with Signatures

Jürgen Pfitzenmaier

26. Juli 2004

1  Introduction

This document describes an extension to the OCaml language. The idea for this extension comes from Norman Ramsey[Ram01]. He saw the need for additional constructs in the OCaml signature language and gave examples for their usage. He didn't give an implementation but showed in [Ram01] and [Ram03] how to program without these extensions but with additional effort.
I found the suggestions useful and wrote my own implementation. Its syntax differs slightly from that in [Ram01] to make it easier to use. Experience with an earlier implementation showed that ``lazy module types'' are necessary as a further extension.

2  Constraints for Functors

We can add constraints to the argument or the result of a functor after forming the functor. This does not effect the original functor but only a copy of it.

     module type A = sig type a end
     module type B = sig type b end
     module type F  = functor(A1 : A) -> B
     module type F1 = F with arg type a = int
     module type F2 = F with res type b = char;;

OCaml treats the last two declarations as

     module type F1 = functor (A1 : sig type a = int end) -> sig type b end
     module type F2 = functor (A1 : sig type a end) -> sig type b = char end

arg and res are not keywords but they are the only identifiers allowed between the keywords with and type. Therefor arg and res can be used in other places as normal identifiers.


3  Union of Signatures

We can add two disjoint signatures:

     module type A = sig type a end
     module type B = sig type b end
     module type U = A sig union B;;

OCaml treats the last declaration as

     module type U = sig type a type b end

In the next example the signatures are not disjoint. The types A.t and C.t have equal names but they are not connected by a constraint.

     module type A = sig type t and a end
     module type B = sig type t and b end
     module type U = A sig union B;;  (* error *)

This is a potential error and the compiler gives a warning message:

     Warning: the two types t are literally equal but not connected by a constraint

We must add a constraint saying that the two types are to be considered equal:

     module type A = sig type t and a end
     module type B = sig type t and b end
     module type U = (X : A) sig union (B with type t = X.t);;

OCaml treats the last declaration as:

     module type U = sig type t and a and b end

4  Lazy functor arguments

We can see the need for lazy functor arguments in the next example (from the patched lua interpreter; see below under more examples). We want to express a dependency between two arguments of the same functor while these arguments themselves are functors. To do this we need access to the argument or result module type of a functor. This is given by using functor arg(F) or functor res(F) inside a constraint like in line 12:


   1      module type Core = sig
   2        type state
   3      end
   4      module type BARECODE =
   5        functor (C : Core) -> sig
   6          val init : C.state -> unit
   7      end
   8 
   9      module W = struct
  10        module F =
  11          functor (C1 : BARECODE) ->
  12          functor (C2 : BARECODE with arg type state = functor arg(C1).state) ->
  13          struct
  14          end;;
  15      end;;

Up to here everything seems fine but we can not define a module by applying the functor W.F while imposing constraints on its functor arguments (mind the use of double parentheses in this example):

  16      module CC = functor (C : Core) -> struct let init _ = () end;;
  17      module Fimpl = W.F ( (C1 : BARECODE with arg type state = int) )
  18                         ( (C2 : BARECODE with arg type state = int) );;

The compiler complains about non-matching type declarations:

     Signature mismatch:
     Modules do not match:
       functor (C : sig type state = int end) ->
         sig val init : C.state -> unit end
     is not included in
       BARECODE
     Type declarations do not match:
       type state
     is not included in
       type state = int

The reason for this error is the contravariance of the functor argument: The C1 in line 17 should be a supertype of the C1 in line 11. The solution is to add a constraint for the first functor argument to make the C1 in line 11 less general. But this constraint must respect the constraint on C2 in line 12. This can only be expressed by a circular dependency between C1 and C2 and marking the name of the second functor argument as lazy:

   module W = struct
     module F =
       functor (C1 : BARECODE) with lazy (C2) (arg type state = functor arg(C2).state) ->
       functor (C2 : BARECODE  with            arg type state = functor arg(C1).state) ->
       struct
       end;;
   end;;
   module CC = functor (C : Core) -> struct let init x = () end;;
   module Fimpl = W.F ( (CC : BARECODE with arg type state = int) )
                      ( (CC : BARECODE with arg type state = int) );;

A functor with a lazy module name must be fully applied.

   module Fpart = W.F ( (CC : BARECODE with arg type state = int) );;
This partial application raises an error message:

   This functor application depends on the following unresolved lazy names:
   [C2]

This limitation will be lifted in the next version.


5  Renaming

We can also rename an item in a signature. Take the following module type:

     module type A = sig
       type t
       val f : unit -> int
       module type B = sig
         type x
       end
     end;;

Rename a type in a module type:

     module type A2 = sig rename (A; type t = tnew);;
to get:

     module type A2 =
       sig type tnew val f : unit -> int module type B = sig type x end end

Rename a type deep inside:

     module type A3 = sig rename (A; type B.x = u);;
to get:

     module type A3 =
       sig type t val f : unit -> int module type B = sig type u end end

We can rename not only types but also values:

     module type A4 = sig rename (A; val f = fnew);;
to get:

     module type A4 =
       sig type t val fnew : unit -> int module type B = sig type x end end

We can rename several items at once:

     module type A5 = sig rename (A; type t = tnew; val f = fnew);;
to get:

     module type A5 =
     sig type tnew val fnew : unit -> int module type B = sig type x end end

The requests for renaming are separated by semicolons. The requests are ordered from left to right with leftmost renaming taking place first. As long as the requests do not overlap, the order in which they appear is not important.


6  Installation and Usage

Get the patches as a gzipped diff-file from here. Unpack the diff with

      gzip -d sigpatch.diff.gz
Go to a directory where a fresh OCaml 3.08.0 distribution lives and apply the patch

      cd ocaml-3.08.0
      patch -p1 < sigpatch.diff
Run configure as usual but use a different directory for installation. This way you avoid overwriting your existing OCaml binaries.

      ./configure --prefix yournewdirectory
Promote the patch to get the dependencies right:

      make promote-patch
Then compile and install the patched OCaml as usual:

      make world
      make install

ocaml, ocamlc and ocamldep all use 2 flags to enable the new extensions:


      -use-functors
enables only the `functor with arg type' extension.


      -use-sigcalc
enables the `sig union', `with lazy' and renaming extensions.

You must use both flags to enable all of the extensions.


7  More Examples

The first real application for these extensions is the the lua-interpreter from the download area of www.cminusminus.org. My patch for this interpreter implements most of the changes Ramsey [Ram01] wanted to see.
The patch is not kept up to date on a daily basis. You have to use the version of the lua-interpreter with roughly the same date stamp as the patch. Slightly newer versions of the lua interpreter should be ok as it is only slowly evolving. The patch for for lua-ml-20040721.tar.gz is
here.
Unpack the source of the lua interpreter:

      tar xzf lua-ml-20040721.tar.gz
and apply the patch in the newly created directory lua-ml:

      cd lua-ml
      zcat  luap-20040721.diff.gz | patch -p1
then build the lua interpreter as usual:

      make -f Makefile.alone depend
      make -f Makefile.alone all

8  Outlook

Some features will be implemented in the near future:

9  Bugs and Limitations

The implementation is still in an experimental state and contains bugs and/or undocumented behaviour. Among them are the following:

10  Contact

Send comments to pfitzen@pfitzenmaier.de
Please mention the version of the patch (as given by: ocamlc -v) when reporting bugs and send a small example that produces the offending behaviour.

References

[Ram01]
Norman Ramsey. Toward a Calculus of Signatures. from http://www.eecs.harvard.edu/~nr/, October 2001.

[Ram03]
Norman Ramsey. ML Module Mania: A Type-Safe, Separately Compiled, Extensible Interpreter. from http://www.eecs.harvard.edu/~nr/pubs/, October 2003.

This document was translated from LATEX by HEVEA.