SML/NJ provides some additional language features beyond those specified in the SML \'97 Definition.
Vector expressions and patterns
Vectors are homogeneous, immutable arrays (see the Vector structure). Vectors are a standard feature of SML \'97, but SML/NJ also has special syntax for vector expressions and vector patterns. In SML \'97, vectors can be created only by calling functions from the Vector structure, and cannot be pattern-matched.
The vector expression
#[exp0, ..., expn]
(where n >= 0
) creates a vector of length n+1
whose
elements are the values of the corresponding subexpressions. As with
other aggregate expressions, the element expressions are evaluated
from left to right. Vectors may be pattern-matched by vector patterns
of the form
#[pat0, ..., patn]
Such a pattern will only match a vector value of the same length.
Vector expressions and vector patterns have more compact and efficient runtime representations than lists, and are comparable in cost to records.
Or-patterns
SML/NJ has also extended the syntax of patterns to allow “or-patterns.” The basic syntax is:
(apat1 | ... | apatn)
where the apati
are atomic patterns.
The other restriction is that the variables bound in each
apati
must be the same, and have the same types.
A simple example is:
fun f ("y" | "yes") = true
| f _ = false
which has the same meaning as:
fun f "y" = true
| f "yes" = true
| f _ = false
Quote and Antiquote
The original use of ML was as a Meta Language for manipulating terms in an object language (typically a logic; originally LCF, or Scott's “Logic for Computable Functions”). The original LCF/ML had features to parse one particular object language (called the OL). Standard ML of New Jersey has support for arbitrary object languages, with user supplied object-language parsing.
Higher-order Modules
The module system of Standard ML has always supported first-order parametric modules in the form of functors (aka module functions). But there are occasions when one would like to parameterize over functors as well as structures, which requires a truly higher-order module system (see, for instance, this powerset functor example). As of Version 0.93 (Feb 1993) SML/NJ has provided a higher-order extension of the module system.
Parameterization over functors can be provided in a straightforward way by allowing functors to be components of structures. Syntactically this can be accomplished merely by allowing functor declarations in of structure bodies, and by providing syntax for functor specifications in signatures. Functor specifications were already part of the module syntax of the 1990 Definition of Standard ML (Figure 8, p. 14), so we have implemented that syntax and added it to the _spec syntax class (Figure 7, p. 13). In addition, it is convenient to have a way of declaring functor signatures and some syntactic sugar for curried functor definitions and partial application of curried functors, and so these are provided. This extension is an “upward-compatible” enrichment of the language that breaks no existing programs.
Functors as structure components.
In the extended language, a signature can contain a functor specification:
signature SIG =
sig
type t
val a : t
functor F(X: sig type s
val b: s
end) : sig val x : t * X.s end
end
To match such a signature, a structure is allowed to contain a functor declaration:
structure S : SIG =
struct
type t = int
val a = 3
functor F(X: sig type s val b: s end) = struct val x = (a,X.b) end
end
This makes it possible to define higher-order functors by including a functor as a component of a parameter structure or of a result structure. The case of a functor parameter is illustrated by the following example.
signature MONOID =
sig
type t
val plus: t*t -> t
val e: t
end;
(* functor signature declaration *)
funsig PROD (structure M: MONOID
structure N: MONOID) = MONOID
functor Square(structure X: MONOID
functor Prod: PROD): MONOID =
Prod(structure M = X
structure N = X);
Note that this example involves the definition of a functor
signature PROD
.
Currently functor signature declarations take one of the following forms:
funsig funid (stridi: sigexpi) = sigexp funsig funid (specs) = sigexp
Warning
|
This syntax is viewed as provisional and subject to change (but it hasn't changed since 0.93). |
A common use of functors returning functors in their result is to approximate a curried functor with multiple parameters. Here is how one might define a curried monoid product functor:
functor CurriedProd (M: MONOID) =
struct
functor Prod1 (N: MONOID) : MONOID =
struct
type t = M.t * N.t
val e = (M.e, N.e)
fun plus((m1,n1),(m2,n2))=(M.plus(m1,m2),N.plus(n1,n2))
end;
end
This works, but the partial application of this functor is rather awkward because it requires the explicit creation of an intermediate structure:
structure IntMonoid =
struct
type t = int
val e = 0
val plus = (op +): int*int -> int
end;
structure Temp = CurriedProd(IntMonoid);
functor ProdInt = Temp.Prod1;
To simplify the use of this sort of functor, some derived forms provide syntactic sugar for curried functor definition and partial application. Thus the above example can be written:
functor CurriedProd (M: MONOID) (N: MONOID) : MONOID =
struct
type t = M.t * N.t
val e = (M.e, N.e)
fun plus((m1,n1),(m2,n2))=(M.plus(m1,m2),N.plus(n1,n2))
end;
functor ProdInt = CurriedProd(IntMonoid);
The syntax for curried forms of functor signature and functor declarations and for the corresponding partial applications can be summarized as follows:
funsig funsigid (par1) ... (parn) = sigexp functor funid (par1) ... (parn) = strexp functor funid1 = funid2 (arg1) ... (argn) structure strid = funid (arg1) ... (argn)
where
par ::= id : sigexp | specs arg ::= strexp | dec
In the case of a partial application defining a functor, it is assumed
that the funid2
on the right hand side takes more than n
arguments, while in the case of the structure declaration funid
should take exactly n arguments. As a degenerate case where n=0
we have identity functor declarations:
functor funid1 = funid2
There is also a "let" form of functor expression:
fctexp ::= let dec in fctexp end
which can only be used in functor definitions of the form:
functor funid = let dec in fctexp end
The curried functor declaration
functor fctid (par1) ... (parn) = strexp
is a derived form that is translated into the following declaration
functor F (par1) = struct functor %fct% (par2) ... (parn) = strexp end
and the declarations
structure S = F (arg1) ... (argn) functor G = F (arg1) ... (argn)
are derived forms expanding into (respectively):
local structure %hidden% = F (arg1) in structure S = %hidden%.%fct% (arg2) ... (argn) end
and
local structure %hidden% = F (arg1) ... (argn) in functor G = %hidden%.%fct% end
Currently there is no checking that a complete set of arguments is supplied when a curried functor is applied to define a structure, as illustrated by the following example:
functor Foo (X: sig type s end) (Y: sig type t end) =
struct
type u = X.s * Y.t
end
structure A = struct type s = int end
structure S = Foo (A) (* Foo A yields a (useless) structure *)
functor G = Foo (A) (* Foo A yields a functor *)
Of course, the structure S
defined in this way is useless, since we cannot use the
pseudo-identifier %fct%
to select its functor component. This is a bug, and arity
checking to prevent this sort of error should be added in some future release.