next up previous contents
Next: The batch compiler Up: Moscow ML Owner's Manual Previous: The interactive system

Modules and compilation units

 

Basic concepts

A Moscow ML program can consist of one or more compilation units, or units for short. A compilation unit consists of an optional unit signature and a unit body. The unit signature specifies the contents of the unit; it is an interface to the unit. The unit body declares the contents of the unit; it provides an implementation of the unit. The following analogies may be helpful:

tabular319

The unit body is always present, whereas the signature can be omitted. When the unit signature is present, it is called the explicit signature to distinguish it from the signature inferred when elaborating the unit body. When present, the explicit signature must be matched by the body, and only those identifiers specified in the signature are visible outside the unit. If no signature is given, all identifiers visible at the end of the unit body are visible outside the unit.

Units are closely associated with files, as in Modula-2. The body of the unit called `U' is defined in a file called `U.sml', and its explicit signature (if any) in file `U.sig'.

tabular325

Units without explicit signature

 

A unit U without an explicit signature consists of a file U.sml containing

program344

This is the same as a simple SML structure declaration. There must be no corresponding explicit signature file U.sig.

Units with explicit signature

 

A unit U with an explicit signature consists of a signature file U.sig containing

program349

and a file U.sml, containing

program352

This is the same as a SML structure declaration with an opaque signature constraint. Note that the file name, signature name, and structure name must be the same. The notation `U :> U' is an opaque signature constraintgif, meaning that other units have no access to the internals of U.sml, only to the signature U.sig.

To illustrate the difference between transparent and opaque signature constraints, consider the Standard ML (not Moscow ML) declarations:

program359

Given these declarations, the expression S.x+33 will typecheck. Although the signature SIG just says that there exists a type t, constraining S with SIG does not hide the fact that S.x is actually an integer.

On the other hand, an opaque signature constraint, as in an abstraction and in Moscow ML units, does hide the true nature of t and x:

program371

After this declaration, M.x+33 would fail to typecheck: the type checker cannot see that M.t is int and M.x is an integer. Often such hiding is just what is needed for software engineering purposes.

Syntax of unit signatures

 

Moscow ML unit signatures are very similar to Standard ML signatures as defined in [6, Figure 7,]; the differences are explained below. A unit signature (in file U.sig) has the form:

quot380

Note:

  1. Type abbreviations type typbind are permitted in signatures.
  2. There are no structure specifications and no sharing specifications.
  3. No type, value, or exception may be specified twice at top-level.
  4. A local specification can be used only to restrict the scope of open specifications and type abbreviations.
  5. An open specification can appear only inside local.
  6. The `signature unitid = sig' and `end' parts may be left out, although this is not recommended.

Restriction (2) is the most significant one. Restriction (3), and restrictions similar to (4) and (5), are imposed by the Standard ML of New Jersey implementation also.

Syntax of unit bodies

A unit body (in file U.sml) has the form:

quot439

A long identifier can refer to entities declared in other units. In Moscow ML, the syntax of long identifiers is:

quot452

where unitid and id are arbitrary SML identifiers (either symbolic or alphanumeric).

A qualified identifier unitid.id denotes an entity id declared in the compilation unit unitid. A qualified identifier can denote either a value variable, a value constructor, an exception constructor, or a type constructor. As in Standard ML, a longid appearing in a defining position, such as a value variable in a pattern, cannot have a qualifier: the identifier being defined will always belong to the current unit.

An open declaration has the form

program467

where U1 tex2html_wrap_inline1758tex2html_wrap_inline1758open U declaration can reference identifiers declared in U without explicitly specifying the name of the unit, subject to the usual scope rules of Standard ML. That is, one can use id instead of U.id.

In the interactive system, a unit must be loaded before it can be opened. In the batch compilation system, the linker links in (only) the needed declarations from opened units.

A unit body U.sml must elaborate to a structure S. If there is an explicit signature U.sig corresponding to U.sml, then the resulting structure must match the explicit signature. As in Standard ML (but in contrast to Caml Light), no reference is made to the signature while elaborating the unit body.

An example program consisting of three units

 

To illustrate the module system, we present a tiny program working with arithmetic expressions. It consists of three units Expr, Reduce, and Evaluate. This example is in mosml/examples/manual.

File Expr.sml below contains structure Expr, which defines a datatype expr for representing expressions and a function show to display them. It has no signature constraint and therefore exports both the datatype and the function:

program490

File Reduce.sig below contains the signature Reduce, which specifies a function for reducing expressions. It mentions the type Expr.expr from Expr:

program496

File Reduce.sml below contains the structure Reduce, which has a signature constraint, and therefore exports only the function reduce specified in the signature:

program501

File Evaluate.sig below contains the signature Evaluate, which specifies a function eval for evaluating expressions, and a function test. Note the use of `open Expr' to make the type expr refer to Expr.expr:

program510

File Evaluate.sml below contains structure Evaluate, which has a signature constraint, and mentions unit Expr as well as Reduce:

program516

Compiling, linking, and loading units

Units can be compiled and linked using the batch compiler mosmlc; see Section 5. Units compiled with option -c can be linked together. Use mosml -o mosmlout A.uo to produce a linked executable bytecode file mosmlout which will invoke the runtime system camlrunm. Use mosml -noheader -o mosmlout A.uo to produce a linked bytecode file which can be executed by camlrunm mosmlout. The linker will automatically link any required bytecode files into mosmlout. See Section 5.2 for more options.

Units can also be compiled from and loaded into the interactive system mosml using the primitives compile and load; see Section 3.4 above.

Organizing programs for compatibility with SML Modules

 

Moscow ML 1.42 and Standard ML of New Jersey (version 109 and later) implement the same core language, and many of the same libraries. Here we give advice on organizing structures and signatures so that they can be compiled by both systems.

Assume we have a software system consisting of three structures A, B, and C, where A and B each have a signature constraint, but C does not. Assume further that C depends on A and B. (There must be no functors or nested structures in A and B). We organize them in five files:

quot535

Now we can compile these files using mosmlc and load them into a mosml session as follows (where `$' is the shell prompt and `-' is the ML prompt):

program542

Or, we can load and compile them in an SML/NJ session as follows:

program544

Hence the same source files can be used unmodified in both systems. (However, as of version 109.14, Standard ML of New Jersey does not support the new syntax structure A :> A = ... for opaque signature constraints. For this reason, Moscow ML accepts the old syntax abstraction A : A = ... also).

Note that in Moscow ML, mosmlc will create bytecode files A.ui, A.uo, and so on. The load function does not perform any compilation and hence is very fast. If the source files do not change, there is no need to recompile them with mosmlc, which may save much time.

If the source files do change, and have to be recompiled at every use, it may be more practical to use the function compile:

program553

Matching a unit body against a signature

 

A unit body S matches a signature SIG under the conditions described in the Definition of Standard ML [6, Section 5.12,]. Roughly, this means:

Moreover, to facilitate separate compilation, there are some representation constraints:

  1. If the specified argument type of a value constructor (in a datatype specification) is an explicit tuple or record, then the declared argument type must be an explicit tuple or record also, and vice versa. This restriction does not apply if there is only one constructor in the datatype.
  2. The order of value constructors in a datatype specification must be the same as in the matching datatype declaration.


next up previous contents
Next: The batch compiler Up: Moscow ML Owner's Manual Previous: The interactive system

Moscow ML 1.42