Download presentation
Presentation is loading. Please wait.
Published byDenis Cox Modified over 9 years ago
1
Module Language
2
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.
3
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.
4
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
5
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
6
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
7
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
8
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 q = PersQueue.empty val q' = PersQueue.insert (1, q) val q'' = PersQueue.insert (2, q) val (x'', _) = PersQueue.remove q'' (* 2 *) val (x', _) = PersQueue.remove q' (* 1 *)
9
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.
10
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
11
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
12
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.
13
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.
14
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. Enrichment and realization
15
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.
16
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.
17
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.
18
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).
19
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
20
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.
21
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.
Similar presentations
© 2024 SlidePlayer.com. Inc.
All rights reserved.