Skip to main content

Additional Modules Syntax

By Thea Brick, January 2023

Modules in general are fairly complex. Due to this, we mostly focused on a subset of the available syntax. That is not to these things are not useful though.

The open/include Keyword for Modules

These are very simple, but often useful declarations. The open declaration takes a structure and places everything defined within it into scope. For instance, if you were to write open Int, then you could just call toString to get the Int.toString function instead.

The include keyword is essentially identical, but instead operates on the signature/specification level. Writing include SIGNATURE essentially inserts all the specification defined in the signature into wherever the include is placed.

The where Keyword for Modules

The where syntax allows the sharing of abstract type information for a structure which otherwise uses opaque ascription. This is useful because we generally want to always use opaque ascription, as not letting outside programs mess with internal representations is important, yet there are some instances where knowledge of the representation is essential, but it doesn't make sense to make the type concrete.

There where keyword appears after the signature which it is being applied to. Here is an example of what the where keyword does.

signature EXAMPLE =
sig
type t (* abstract *)
type u (* abstract *)
type v (* abstract *)
end

signature EXAMPLE2 = EXAMPLE where type t = int

The type t is no longer abstract in EXAMPLE2 rather it is defined to be int. Likewise this can be done for multiple types, either through chaining wheres or through the and keyword.

signature EXAMPLE3 = EXAMPLE where type t = int and type u = string

Only type v remains abstract.

The sharing Keyword for Modules

Sharing (in SML) is stating that two types need to be the same. There are numerous complexities with this. In general, if one of the types that is being shared is not abstract, then it cannot be shared.

Here is a really basic example of type sharing:

signature EXAMPLE =
sig
type t (* abstract *)
type u (* abstract *)
sharing type t = u
end

This enforces that in any structure implementing EXAMPLE, the types t and u must be the same. This might seem a little silly, becaue we could easily write the following to do the same thing:

signature EXAMPLE2 =
sig
type t (* abstract *)
type u = t
end

This does do the same thing. And in fact, it is prefered to the first example, so sharing should be avoided if possible. Yet, there are some scenarios where sharing is useful. For instance, enforcing two different structures use the same type:

signature EXAMPLE3 =
sig
type t (* abstract *)
end

signature EXAMPLE4 =
sig
structure S1 : EXAMPLE3
structure S2 : EXAMPLE3
sharing type S1.t = S2.t
end

In this case, the two structures S1 and S2 must have the same type for t, and without the sharing specification this could not be enforced.