Download presentation
Presentation is loading. Please wait.
Published byΘαλής Κόσμος Μαυρογένης Modified over 6 years ago
1
value : type : function :: structure : signature : functor
Modules value : type : function :: structure : signature : functor cs7120(Prasad) L8-MOD
2
SML features Programming in the small Programming in the large
Reliability Correctness (types) Robustness (exceptions) Efficiency Data Structures and Algorithms (lists, arrays) Abstraction Data (patterns) Control (higher-order functions) Declarative (enhances programmer productivity) Programming in the large Cluster related definitions; hierarchical organization of programs (structures) Encapsulation (localization) of implementation details, for clarity and modifiability (abstype, signatures) Reusability and interchangeability of parts (functors). Separate compilation. Standard libraries. Programming in the large: Modularity and sharing issues (large size) (conceptual and organizational aspects) Evolutionary change issues (long life) (protecting clients from behavior invariant changes) Flexibility for evolution thro reuse : Convenience and rapid development cs7120(Prasad) L8-MOD
3
Customization of Modules
Controlling visibility of names (Managing environments) To avoid potential name conflicts. Multiple implementations of an ADT (e.g, Table). Similar operations on different ADTs (e.g., delete on Set, List, Table, etc). Programmer choice of names, to conform to domain vocabulary. To encapsulate representation details. To enable transparent changes to the impl. To preserve data integrity (invariants) Instantiation (Specializing w.r.t. types) Warning: Indiscriminate opening of structures may lead to bugs involving inadvertent shadowing of existing bindings. Information Hiding for clarity + for changes + for correctness (reliability) --- Reuse + Flexibility cs7120(Prasad) L8-MOD
4
SML Constructs Structure Signature Functor Collection of definitions
E.g., Ada/Java packages, C++/Java classes, etc Signature Type declarations of the names E.g., Ada package specification, Java interfaces, etc Functor Maps a structure to another structure Parameterized structures E.g., Ada generic units, C++ templates, Java functions with interface/class parameters, etc In general, structure can be thought of as packaging up types with operations to provide an interpretation of that type. E.g., (int, +,*), (int,+,-,0) etc. Systematic name management The following QueueImpl example illustrates (1) the use of amortization to assess and compare the complexity wrt to std list-based impl. (which is quadratic as opposed to linear for enq-deq sequences) (2) the use of data invariants in the “correct” impl. of the behavior. cs7120(Prasad) L8-MOD
5
datatype 'a t = Q of ('a list * 'a list); exception E;
structure QueueImpl = struct datatype 'a t = Q of ('a list * 'a list); exception E; val empty = Q([],[]); fun norm (Q([],ts)) = Q(rev ts, []) | norm q = q; fun enq(Q(hs,ts), x) = norm(Q(hs, x::ts)); fun null(Q([],[])) = true | null _ = false; fun hd(Q(x::_,_)) = x | hd(Q([],_)) = raise E; fun deq(Q(x::hs,ts)) = norm(Q(hs,ts)) | deq(Q([],_)) = raise E; end; To enable convenient access to both the front and the rear of the queue in list representation, encode queue front [x1,x2,…,xn,y1,y2,…,ym] rear (non-uniquely) as a pair of lists: ([x1, x2,…,xn], [ym, …, y2, y1]) This mapping is not unique, so define representation invariant to filter illegal representations. In particular, all queue forming operations ensure it, and all other operations exploit it. NORMALIZE: (*Normalized queue, if nonempty, has nonempty heads list*) ENQ: (*norm has an effect if input queue is empty*) DEQ:(*normalize in case heads become empty*) cs7120(Prasad) L8-MOD
6
structure QueueImpl : sig datatype 'a t = Q of 'a list * 'a list
exception E val deq : 'a t -> 'a t val empty : 'a t val enq : 'a t * 'a -> 'a t val hd : 'a t -> 'a val norm : 'a t -> 'a t val null : 'a t -> bool end; Traditional implementation of queues in terms of a single list makes one of the enqueue or dequeue operation to be o(1), while the other is o(n). The current implementation makes both of them o(1) with the proviso that once in a while it does go into normalization mode. It is reasonably fast on the average but may deteriorate for specific scenerios. cs7120(Prasad) L8-MOD
7
Information Hiding Concerns
The implementation details of QueueImpl are exported. Client code can use pattern matching to manipulate queues. Client code can break if the implementation of QueueImpl is changed. Client code can ignore the FIFO discipline. Semantics of queue (invariants) can be violated. Possible Solution : Restrict the view cs7120(Prasad) L8-MOD
8
Transparent Signature Constraints
signature Qtip = sig type ’a t val deq : 'a t -> 'a t val empty : 'a t val enq : 'a t * 'a -> 'a t val hd : 'a t -> 'a val null : 'a t -> bool end; structure Queue : Qtip = QueueImpl; changing view : Hiding concrete impl in terms of “Q”-lists and “norm”-function Transparent signatures effectively introduce “compound” aliases for existing names. The values constructed using Queue operations are in fact represented in terms of constructors in QueueImpl. cs7120(Prasad) L8-MOD
9
- Queue.norm; (* error *) - Queue.hd;
- QueueImpl.norm; val it = fn : 'a QueueImpl.t -> 'a QueueImpl.t - Queue.norm; (* error *) - Queue.hd; val it = fn : 'a QueueImpl.t -> 'a - Queue.Q; (* error *) - Queue.empty = QueueImpl.Q ([],[]); - open QueueImpl; - norm(Queue.enq(Queue.empty, “a”)); val it = Q (["a"],[]) : string t Even though the names visible in the structure Queue is a subset of the names visible in QueueImpl, the representation is not really hidden. That is, it is possible to invoke all operations defined in QueueImpl on Queue values. This approach does not enforce data abstraction. (Requires programmer discipline.) cs7120(Prasad) L8-MOD
10
Opaque Signature Constraints
structure Queue :> Qtip = QueueImpl; This enforces data abstraction. Only operations that are explicitly defined on Queue can be invoked on Queue-values. QueueImpl.norm(Queue.enq(Queue.empty,“a”)); (* error *) Equality tests (based on the representation) are banned. Queue.empty = QueueImpl.Q ([],[])); (* error *) To allow equality tests, use eqtype ’a t instead of type ’a t. (Cf. Ada’s Limited Private Types and Private Types) This approach suppresses representation details for all the types. In case one needs to export concrete details for some of the types, abstype form of the declaration is required. In the Dictionary example, if key must be a string, then opaque constraints are not suitable. cs7120(Prasad) L8-MOD
11
signature SIG = sig val i : int; type t; val x : t
val f:t*t -> bool end; structure STRUC = struct val i = 3; type t = int; val x = 4; fun f(a,b) = (a=b) open STRUC; f(x,x); f(i,x); (*val it = false : bool*) structure STRUC’:> SIG = STRUC; open STRUC’; f(i,x); (* type error *) Type t created using opaque signatures is regarded as distinct from any other type (including int and other type t created from the same structure.) structure STRUC : sig type t = int val f : ''a * ''a -> bool val i : int val x : int end; opening STRUC’; val x : t val f : t * t -> bool stdIn: Error: operator and operand don't agree [tycon mismatch] operator domain: t * t operand: int * t in expression: f (i,x) cs7120(Prasad) L8-MOD
12
(* val it = false : bool*)
signature SIG = sig val i : int; type t; val x : t val f:t*t -> bool end; structure STRUC = struct val i = 3; type t = int; val x = 4; fun f(a,b) = (a=b) signature SIG’ = SIG where type t = int; structure STRUC’:> SIG’ = STRUC; open STRUC’; f(i,x); (* val it = false : bool*) Type t being int is visible because where-clause explicitly exports it. signature SIG' = sig val i : int type t = int val x : t val f : t * t -> bool end; cs7120(Prasad) L8-MOD
13
Signature Matching structure strId : sigExp = strExp ; (* target candidate *) Target signature expresses a set of constraints that the candidate structure must satisfy. Informally, the candidate’s signature may have more components than are required by the target, may have more definitions of types than are required, and may have value components with more general types. Customization of structures : controlling visibility of names, type instantiation strId obtained by applying Target window onto strExp. Matching: For a candidate structure to match a target signature, its principal type of candidate structure should match target signature. Matching is modulo the ordering of the components and equivalence relation due to type equivalences (aliases) cs7120(Prasad) L8-MOD
14
QUEUE_WITH_EMPTY matches QUEUE
signature QUEUE = sig type 'a queue exception Empty val empty : 'a queue val insert : 'a * 'a queue -> 'a queue val remove : 'a queue -> 'a * 'a queue end signature QUEUE_WITH_EMPTY = include QUEUE val is_empty : 'a queue -> bool Candidate has more components cs7120(Prasad) L8-MOD
15
QUEUE_AS_LISTS matches QUEUE
signature QUEUE = sig type 'a queue exception Empty val empty : 'a queue val insert : 'a * 'a queue -> 'a queue val remove : 'a queue -> 'a * 'a queue end signature QUEUE_AS_LISTS = QUEUE where type 'a queue = 'a list * 'a list Candidate is more defined cs7120(Prasad) L8-MOD
16
Equivalent: QUEUE_AS_LIST and QUEUE_AS_LIST0
signature QUEUE = sig type 'a queue exception Empty val empty : 'a queue val insert : 'a * 'a queue -> 'a queue val remove : 'a queue -> 'a * 'a queue end signature QUEUE_AS_LIST0 = QUEUE where type 'a queue = 'a list signature QUEUE_AS_LIST = sig type 'a queue = 'a list exception Empty val empty : 'a list val insert : 'a * 'a list -> 'a list val remove : 'a list -> 'a * 'a list end cs7120(Prasad) L8-MOD
17
MERGEABLE_QUEUE matches MERGEABLE_INT_QUEUE
signature MERGEABLE_QUEUE = sig include QUEUE val merge : 'a queue * 'a queue -> 'a queue end signature MERGEABLE_INT_QUEUE = val merge : int queue * int queue -> int queue Candidate is more general (polymorphic) cs7120(Prasad) L8-MOD
18
RBT_DT matches RBT signature RBT_DT = sig datatype 'a rbt = Empty |
Red of 'a rbt * 'a * 'a rbt | Black of 'a rbt * 'a * 'a rbt end signature RBT = type 'a rbt val Empty : 'a rbt val Red : 'a rbt * 'a * 'a rbt -> 'a rbt cs7120(Prasad) L8-MOD
19
(cont’d) Every type specification (resp. datatype definition) in the target must have a matching (resp. equivalent) type specification (resp. datatype definition) in the candidate. Every exception specification in the target must have an equivalent exception specification in the candidate. Every value specification in the target must be matched by a value specification in the candidate with at least as general a type. MATCHING: candidate signature enriches and realizes target signature (Source: Harper’s book on SML) c enriches t if t can be obtained by dropping components and specializing types. c realizes t if t can be obtained from c by “forgetting” the definitions of some of t’s type components. c matches t if there exists s such that c enriches s and s realizes t. cs7120(Prasad) L8-MOD
20
Relationship between Structures and Signatures
In ML, a signature may serve as an interface for many different structures, and that a structure may implement many different signatures (interchangeability of components) ML, C++, Java† : many-to-many Modula : one-to-one Ada : many-to-one. Every structure has a principal signature, with the property that all other signatures for that structure are more restrictive than the principal signature. ML tries to separate type definition from visibility control issues. (cf. public/private) cs7120(Prasad) L8-MOD
21
Functors A functor maps structures to structure.
A functor can be viewed as a reusable unit that contains well-defined “sockets for plug-ins”. Parameterized/Generic modules. Useful in “client-server paradigm” applications exploiting interchangeability of parts with common interface. Developing floating point libraries that work with different precision implementations. Building common test harness to verify various implementations of an abstract data type. Selling point for OOP: supports modular design 1) clear separation of responsibilities and definition of interfaces ) scheme for factoring commonality ML: achieves (1) and partially supports (2) --) lacks inheritance to support code sharing among structures ) lacks dynamic binding Functor in Java lingo : maps classes to a class where the formals is described using interfaces. cs7120(Prasad) L8-MOD
22
Example signature Order = sig
eqtype et ; val le : et -> et -> bool end; functor MkOrdSet(Ord:Order) = struct exception EmptySetEX; datatype set = SET of (Ord.et list) val empty = SET nil fun insert (SET s) n = SET (n::s) fun member (SET s) n = (List.exists (fn x => (x = n)) s) fun min (SET s) = if (null(s)) then raise EmptySetEX else foldr (fn (x,r) => if (Ord.le r x) then r else x) (hd s) (tl s); Improves upon user-supervised sharing of bindings by defining a bunch of functions in common environment. Representation exported. min uses the field in the formal structure argument. no semantic constraints on le. It can be a linear order, partial order, =, or plain constant function mapping pairs to true. Structure argument : Modularity issue (unit of use) cs7120(Prasad) L8-MOD
23
open intset; structure OrdInt: Order = struct type et = int;
fun le (x:et) y = x <= y end; structure intset = MkOrdSet(OrdInt); open intset; (min empty); (* uncaught exception *) val s1 = (insert empty 5); val s2 = (insert s1 3); (* val s2 = SET [3,5] : set *) (min (insert s2 8)); (* val it = 3 : OrdInt.et *) (* structure intset : sig datatype set = SET of Ord.et list exception EmptySetEX val empty : set val insert : set -> Ord.et -> set val member : set -> Ord.et -> bool val min : set -> Ord.et end *) cs7120(Prasad) L8-MOD
24
Example Functor (Component Reuse)
signature ORDER = sig type t; val compare: t*t -> bool end; structure StringOrder: ORDER = struct type t = string; val compare = String.compare ... structure StringDict = Dictionary (StringOrder); (* structure StringDict : sig type key = StringOrder.t type 'a t exception E of key val empty : 'a t val lookup : 'a t * Key.t -> 'a val update : 'a t * key * 'a -> 'a t end *) Observe the type-equivalence key = Key.t = StringOrder.t val x = GREATER; (* val x = GREATER : order*) String.compare("ab", "ef"); (* val it = LESS : order *) cs7120(Prasad) L8-MOD
25
functor Dictionary (Key: ORDER) = struct type key = Key.t;
abstype 'a t = Lf | B of key *'a*'a t *'a t with exception E of key; val empty = Lf; fun lookup (B(a,x,t1,t2), b) = (case Key.compare(a,b) of GREATER => lookup(t1, b) | EQUAL => x | LESS => lookup(t2, b)) | lookup (Lf, b) = raise E b; fun update (Lf, b, y) = B(b, y, Lf, Lf) | update (B(a,x,t1,t2), b, y) = GREATER => B(a, x, update(t1,b,y), t2) | EQUAL => B(a, y, t1, t2) | LESS => B(a, x,t1,update(t2,b,y))); end end; Equality constraints among types expressible Representation hidden because of abstype. cs7120(Prasad) L8-MOD
26
(cont’d) infix |< ; fun (d |< (k,x)) = StringDict.update(d,k,x);
val dict = StringDict.empty |< (“abc”, 2) |< (“nbc”, 11) |< (“dsc”, 53) |< (“tlc”,54); (* val dict = - : int StringDict.t *) StringDict.lookup(dict, “dsc”); (* val it = 53 : int *) StringDict.lookup(dict, “cnn”); (* uncaught exception E *) infix 6 |< In contrast with typical imperative/OOP languages where the syntax of the operators are fixed (fixity, precedence and associativity), the functional and logic languages (e.g., ML, Prolog) allow one to declare such information in the program code. So ML expression parser is “customizable” and thus complicated. Note that this aspect is in contrast with the idea of overloading which is permitted in imperative/OOP languages. cs7120(Prasad) L8-MOD
27
Multi-argument Functor
signature INT = sig val i: int end; signature REAL = sig val r: real end; functor Foo(structure I:INT and R: REAL; val x : int) = struct end; structure Int = struct val i = 0 end; structure Real= struct val r = 0.0 end; structure Bar = Foo(structure I = Int and R = Real; val x = 2); - open Bar; opening Bar cs7120(Prasad) L8-MOD
28
Sharing Constraints (for combining / integrating modules)
To express equality constraints among types names. Type sharing sharing type <type> = … = <type> Structure sharing sharing <structure> = … = < structure> Shorthand for sharing of identically named types within the equated structures. Declarative specification of type constraints (equality) The ML interpreter has to propagate all the implicit constraints while type checking or inferring type. cs7120(Prasad) L8-MOD
29
signature ELEMENT = sig type element;
val similar : element * element -> bool; end; signature BTREE = sig structure Element: ELEMENT; eqtype elt; sharing type elt = Element.element; datatype bt = Empty | Node of elt * bt * bt; val leaf : elt -> bt; val build : elt * bt * bt -> bt; val lookup : elt * bt -> bool cs7120(Prasad) L8-MOD
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.