module language

advertisement
Module Language
Module language
• The Standard ML module language comprises the
mechanisms for structuring programs into separate units.
– Program units are called structures.
– A structure consists of a collection of components, including types
and values, that constitute the unit.
– Composition of units to form a larger unit is mediated by a
signature, which describes the components of that unit. A signature
may be thought of as the type of a unit.
– Large units may be structured into hierarchies using
substructures.
– Generic, or parameterized, units may be defined as functors.
Structures
• The fundamental unit of modularity in ML is the
structure.
– A structure consists of a sequence of declarations
comprising the components of the structure.
– A structure may be bound to a structure variable using
a structure binding.
– The components of a structure are accessed using long
identifiers, or paths.
– A structure may also be opened to incorporate all of its
components into the environment.
Example of structure
structure IntLT = struct
type t = int
val lt = (op <)
val eq = (op =)
end
structure IntDiv = struct
type t = int
fun lt (m, n) = (n mod m = 0)
val eq = (op =)
end
Path
• The components of a structure are accessed
using paths (also known as long identifiers or
qualified names).
• The type of IntLT.lt is
IntLT.t * IntLT.t -> bool or
int * int -> bool
• The type of IntDiv.lt is
IntDiv.t * IntDiv.t -> bool or
int * int -> bool
Opening structure
• For example, rather than writing
IntDiv.lt (exp1, exp2) andalso
IntDiv.eq (exp3, exp4)
• we may instead write
let
open IntDiv
in
lt (exp1, exp2) andalso eq
(exp3, exp4)
end
Safe alternative to opening
• Introduce a short (typically one letter) name for
the structures in question to minimize the clutter
of a long path. Thus we might write
let
structure I = IntLT
in
I.lt (exp1, exp2) andalso I.eq
(exp3, exp4)
end
A queue
structure PersQueue = struct
type 'a queue = 'a list * 'a list
val empty = (nil, nil)
fun insert (x, (bs, fs)) = (x::bs, fs)
exception Empty
fun remove (nil, nil) = raise Empty
| remove (bs, f::fs) = (f, (bs, fs))
| remove (bs, nil) = remove (nil, rev bs)
end
val
val
val
val
val
q = PersQueue.empty
q' = PersQueue.insert (1, q)
q'' = PersQueue.insert (2, q)
(x'', _) = PersQueue.remove q''
(x', _) = PersQueue.remove q'
(* 2 *)
(* 1 *)
Signature
• A signature is the type of a structure.
– It describes a structure by specifying each of its components by
giving its name and a description of it. Different sorts of
components have different specifications.
– A type component is specified by giving its arity (number of
arguments) and (optionally) its definition.
– A datatype component is specified by its declaration, which
defines its value constructors and their types.
– An exception component is specified by giving the type of the
values it carries (if any).
– A value component is specified by giving its type scheme.
Signature of an ordered type
• signature ORDERED = sig
type t
val lt : t * t -> bool
val eq : t * t -> bool
end
• signature INT_ORDERED = sig
type t = int
val lt : t * t -> bool
val eq : t * t -> bool
end
Signature of persistent queue
signature QUEUE = sig
type 'a queue
val empty : 'a queue
val insert :'a * 'a queue -> 'a queue
exception Empty
val remove : 'a queue -> 'a * 'a queue
end
Signature matching
• The signature matching governs the formation of complex
structure expressions in the same way that type matching
governs the formation of core language expressions.
• For example, to determine whether a structure binding
structure strid : sigexp = strexp is well-formed,
– we must check that the principal signature of strexp matches the
ascribed signature sigexp.
– The principal signature of a structure expression is the signature
that most accurately describes the structure strexp; it contains the
definitions of all of the types defined in strexp, and the types of all
of its value components.
– We then compare the principal signature of strexp against the
signature sigexp to determine whether or not strexp satisfies the
requirements specified by sigexp.
compare candidate and target signature
1. Every type specification in the target must have
a matching type specification in the
candidate. If the target specifies a definition for
a type, so must the candidate specify an
equivalent definition.
2. Every exception specification in the target must
have an equivalent exception specification in
the candidate.
3. Every value specification in the target must be
matched by a value specification in the
candidate with at least as general a type.
Enrichment and realization
1. A signature S1 enriches a signature S2 if S1 has
at least the components specified in S2, with
the types of value components being at least as
general in S1 as they are in S2.
2. A signature S1 realizes a signature S2 if S1
fulfills at least the type definitions specified in
S2, but is otherwise identical to S2.
Signature matching
• We then say that S1 matches S2 if there exists a
signature S3 such that S1 enriches S3 and S3
realizes S1.
• Put in more operational terms, to determine
whether S1 matches S2,
– we first drop components and specialize types in S1 to
obtain a view S3 of S1 with the same components as S2,
– then check that the type definitions specified by S2 are
provided by the view.
Matching failure
Signature matching can fail for several reasons:
1. The target contains a component not present in the
candidate.
2. The target contains a value component whose type
is not an instance of its type in the candidate.
3. The target defines a type component, that is
defined differently or not defined in the candidate.
Realization
• The signature INT_ORDERED realizes the signature ORDERED
because we may obtain the latter from the former by "forgetting" that
the type component t in the signature INT_ORDERED is defined to
be int.
• The converse fails: ORDERED does not realize INT_ORDERED
because ORDERED does not define the type component t to be int.
• Here is another counterexample to realization. The signature
signature LESS_THAN = sig
type t = int
val lt : t * t -> bool
end
does not realize the signature ORDERED, even though it defines t to
be int, simply because the eq component is missing from the signature
LESS_THAN.
Enrichment
• The signature ORDERED enriches the signature LESS_THAN
because it provides all of the components required by the latter, at
precisely the required types.
• For a more interesting example, consider the signature of monoids,
signature MONOID = sig
type t
val unit : t
val mult : t * t -> t
end
and the signature of groups,
signature GROUP = sig
type t
val unit : t
val mult : t * t -> t
val inv : t -> t
end
The signature GROUP enriches the signature MONOID, as might be
expected (since every group is a monoid).
Signature ascription
• The point of having signatures in the language is to
express the requirement that a given structure have a
given signature.
• This is achieved by signature ascription, the attachment
of a target signature to a structure binding.
• There are two forms of signature ascription, transparent
and opaque, differing only in the extent to which type
definitions are propagated into the scope of the binding.
• Transparent ascription is written as
structure strid : sigexp = strexp
• Opaque ascription is written as
structure strid :> sigexp = strexp
Transparent ascription
• We may use transparent ascription on the binding of
the structure variable IntLT to express the requirement
that the structure implement an ordered type.
structure IntLT : ORDERED = struct
type t = int
val lt = (op <)
val eq = (op =)
end
• Transparent ascription is so-called because the
definition of IntLT.t is not obscured by the ascription;
the equation IntLT.t = int remains valid in the scope of
this declaration.
Opaque ascription
• We may use opaque ascription to specify that a structure implement
queues, and, at the same time, specify that only the operations in the
signature be used to manipulate values of that type. This is achieved
as follows:
• structure Queue :> QUEUE = struct
type 'a queue = 'a list * 'a list
val empty = (nil, nil)
fun insert (x, (bs, fs)) = (x::bs, fs)
exception Empty
fun remove (nil, nil) = raise Empty
| remove (bs, f::fs) = (f, (bs, fs))
| remove (bs, nil) = remove (nil, rev bs)
end
• Opaque ascription is so-called because the definition of
'a Queue.queue is hidden by the binding; the equivalence of the
types 'a Queue.queue and 'a list * 'a list is not propagated into the
scope of the binding.
Download