module
system") ?>
Alice extends the SML module system in various ways, by providing:
Items marked with (*) are not fully implemented in Operette 1.
The last item includes allowing op in signatures, having a withtype derived form in signatures (analoguous to declarations), and allowing redundant parentheses in structure and signature expressions.
A syntax summary is given below.
Alice discards SML's separation between core declarations (dec), structure declarations (strdec), and toplevel declarations (topdec). As a consequence, structures can be declared local to an expression (via let) and functors as well as signatures can be nested into structures. For example:
fun sortIntList ns = let structure Tree = MkBinTree(type t = int) in Tree.toList(foldl Tree.insert Tree.empty ns) end
A direct consequence of allowing functors everywhere is the presence of higher-order functors, even if a bit cumbersome:
functor F(X:S1) = struct functor G(Y:S2) = struct (* ... *) end end structure M = let structure Z = F(X) in G(Y) end
This is exactly how SML/NJ introduces higher-order functors. Alice provides higher-order functors in a more first-class fashion. The above example can be written more directly as:
functor F(X:S1)(Y:S2) = struct (* ... *) end structure M = F(X)(Y)
Alice has real functor expressions. Similar to fun declarations, functor declarations are mere derived forms. The declaration for F above is just sugar for:
structure F = fct(X:S1) => fct(Y:S2) => struct (* ... *) end
The keyword fct starts a functor expression very much like fn begins a function expression in the core language. Functor expressions can be arbitrarily mixed with other structure expressions. In contrast to SML, there is no distinction between structure and functor identifiers (see incompatibilities).
(Note: We decided to keep the keyword structure - and the syntactic classes strid, strexp, etc. - for compatibility reasons, although module might now be more appropriate.)
The syntax for signatures has been extended to contain functor types. For example, functor F can be described by the following signature:
structure F : fct(X:S1) -> fct(Y:S2) -> sig (* ... *) end
As a comfortable derived form the following SML/NJ compatible syntax is provided for functor descriptions in signatures:
functor F(X:S1)(Y:S2) : sig (* ... *) end
functor Id(X: any) = X
Like structures and functors, signatures can also be declared anywhere. In particular, this allows signatures inside structures, and consequently, nested signatures:
signature S = sig signature T = sig (* ... *) end end structure X :> S = struct signature T = sig (* ... *) end end
Such manifest signatures must always be matched exactly (Note: matching of manifest signatures is not checked yet). However, analoguous to types, we allow for abstract signature members:
signature S = sig signature T structure X : T end
Note that this feature renders the type system of Alice undecidable (the same is true for O'Caml, which has a very similar module language). We do not consider this a problem in practice, however, since the simplest program to make the type checker loop already is highly artificial:
signature I = sig signature A functor F(X : sig signature A = A functor F(X : A) : sig end end) : sig end end signature J = sig signature A = I functor F(X : I) : sig end end (* Try to check J <= I *) functor Loop(X : J) = X : I
Currently the compiler has no upper limit on the number of substitutions it does during signature matching, so this example will actually make it loop until memory is exhausted.
Functors often require putting where constraints on signatures to denote exact return types. This can become quite tedious. Alice provides an alternative by generalizing signature identifiers to signature constructors, parameterized over structure values:
signature SET(Elem : sig type t end) = sig type elem = Elem.t type t (* ... *) end functor MakeSet(Elem : sig type t end) :> SET(Elem) = struct (* ... *) end
The same derived forms as for functor declarations apply, so SET and MakeSet can be defined as:
signature SET(type t) = sig type elem = t type t (* ... *) end functor MakeSet(type t) :> SET(type t = t) = struct (* ... *) end
Caveat: parameterized signatures are not yet properly treated in Operette 1.
The module system can be abused to type some more delicate operations, like pickling:
signature UNIT = sig end functor Pickle(type t val x : t) : UNIT
When utilizing modules for this purpose the result of a functor application is uninteresting. Similar to core declarations, wildcards are thus provided as a derived form:
structure _ = Pickle(type t = int val x = 43)
Similarly, wildcards are allowed for functor parameters:
functor F(_ : S) = struct (* don't actually need argument *) end
This is particularly useful in signatures:
signature FF = fct(_ : A) -> B
Signatures can contain fixity specifications:
signature S = sig type t infix 2 ++ val x : t val op++ : t * t -> t end
To match a signature with infix specifications, a structure must provide the same infix status directives. The infix environment is part of a structures principal signature.
Opening a structure with unconstrained infix members pulls in the according infix status in the local environment:
structure M :> S = struct (* ... *) end open M val z = x ++ x
Note that this feature produces a syntactic incompatibility with SML showing up in some rare cases.
The syntax for modules very much resembles the syntax of core language expressions:
| atstrexp | ::= | struct dec end | structure |
| longstrid | structure identifier | ||
| let dec in strexp end | local declarations | ||
| ( strexp ) | parentheses | ||
| appstrexp | ::= | atstrexp | |
| appstrexp atstrexp | functor application | ||
| strexp | ::= | appstrexp | |
| strexp : sigexp | transparent constraint | ||
| strexp :> sigexp | opaque constraint | ||
| fct strpat => strexp | functor | ||
| strpat | ::= | ( strid : sigexp ) |
| atsigexp | ::= | any | top |
| sig spec end | ground signature | ||
| longsigid | signature identifier | ||
| ( sigexp ) | parentheses | ||
| appsigexp | ::= | atsigexp | |
| appsigexp atstrexp | signature application | ||
| sigexp | ::= | appsigexp | |
| fct strpat -> sigexp | functor | ||
| sigexp where rea | specialization | ||
| rea | ::= | type tyvarseq longtycon = ty | |
| signature longsigid strpat1 ... strpatn = sigexp | (n>=0) | ||
| sigbind | ::= | sigid strpat1 ... strpatn = sigexp <and sigbind> | (n>=0) |
| sigdesc | ::= | sigid strpat1 ... strpatn <= sigexp> <and sigdesc> | (n>=0) |
| atstrexp | ::= | ( dec ) | |
| strpat | ::= | ( spec ) | |
| ( _ : sigexp ) | |||
| dec | ::= | functor fstrbind | |
| strbind | ::= | _ <: sigexp> = strexp <and strbind> | |
| fstrbind | ::= | strid strpat1 ... strpatn = strexp <and fstrbind> | (n>=1) |
| spec | ::= | functor fstrdesc | |
| fstrdesc | ::= | strid strpat1 ... strpatn : sigexp <and fstrdesc> | (n>=1) |