SML Module Syntax Cheatsheet
By Thea Brick, January 2023
Signature
Signatures contain specifications which dictate what declarations a structure ascribing to said signature must make. A signature declaration appears as follows:
signature YOUR_SIGNATURE_NAME =
sig
(* zero or more specifications here *)
end
signature ANOTHER_NAME = YOUR_SIGNATURE_NAME
Signatures are generally use all capital letters.
Specifications
The following may appear in a signature:
Specification | Explanation |
---|---|
| Structure must declare a variable called |
| Structure must declare a variable called |
| Structure must declare a type |
| Structure must declare a type |
| Structure must declare |
| Structure must declare an exception |
| Structure must declare a structure |
Structures
A structure is a series declaration which ascribe (or match) the signature.
structure YourStructure : YOUR_SIGNATURE =
struct
(* zero or more declarations matching YOUR_SIGNATURE *)
end
The structure must at least have every declaration specified in the signature, but may contain more.
A structure may not contain signature
and functor
declarations.
The following syntax allows SML to infer the signature to the structure based on the declarations made:
structure YourStructure =
struct
(* declarations matching YOUR_SIGNATURE *)
end
Functors
A functor takes a structure ascribing to a signature and outputs a new structure.
functor YourFunctor(
(* zero or more specifications for the input structure *)
) : YOUR_OUTPUT_SIG =
struct
(* declarations matching YOUR_OUTPUT_SIG *)
end
Here is an example of using this syntax:
functor Combine(
val x : int
val fact : int -> ints
structure A : A_SIG
structure B : B_SIG
) =
struct
(* omitted, x, fact, A, and B may be used in here *)
end
Functor Syntax Sugar
If a functor is taking in only one structure, the following syntax may be used:
functor YourFunctor(YourStructure : YOUR_SIGNATURE) : YOUR_OUTPUT_SIG =
struct
(* declarations matching YOUR_OUTPUT_SIG, YourStructure may be used *)
end
Transparent and Opaque Ascription
The symbol :
describes transparent ascription. The symbol :>
describes
opaque ascription. Both ascriptions only allow things specified to be used.
Opaque limits this further by not letting the implementation of abstract types
to be used. Here is an example:
structure Example :> sig
type 'a t (* abstract *)
val isEmpty : 'a t -> bool
end = struct
type 'a t = 'a list
val isEmpty = fn [] => true | _ => false
val test = 123
end
(* does not compile, but would if transparent ascription was used. *)
val res = Example.isEmpty []
(* will never compile *)
val res2 = Example.test
where
Syntax
The where
keyword allows for using opaque ascription while deliberately
exposing specific abstract types. It appears after the signature where used.
signature EXAMPLE =
sig
type 'a t (* abstract *)
type 'a u
val isEmpty : 'a t -> bool
end
structure Example :> EXAMPLE where type 'a t = 'a list = struct
type 'a t = 'a list
type 'a u = int
val isEmpty = fn [] => true | _ => false
end
(* this will compile now *)
val res = Example.isEmpty []
(* this will not compile *)
val res2 : 'a Example.u = 123