CS 342 Lecture -*- Outline -*- * Modularity and Data Abstraction (Read discussion in MacLennan's 7.4 for background) ** Abstraction techniques: *** by parameterization: x*x + y*y ==> fun f(x,y) = x*x + y*y; f(3,4), ... *** by specification -abstract from body (of procedure) what its effect is (ignore implementation details) kinds: *** Procedural abstraction: extend language's virtual machine with new "operations" *** Data abstraction: extend language's virtual machine with new kinds of objects. A data abstraction (abstract data type) = set of objects and operations that characterize the behavior of the objects. **** Example: lists of integers -------------------------- package INT_LIST is type INT_LIST_TYPE is private; NIL: constant INT_LIST_TYPE; function CONS(X: INTEGER; L: INT_LIST_TYPE) return INT_LIST_TYPE; function CAR(L: INT_LIST_TYPE) return INTEGER; function CDR(X: INT_LIST_TYPE) return INT_LIST_TYPE; private type CELL; --incomplete type INT_LIST_TYPE is access CELL; type CELL is record VALUE: INTEGER; SUCC: INT_LIST_TYPE; end record; NIL: constant INT_LIST_TYPE := null; end; -------------------------- can write a block: -------------------------- declare use INT_LIST; v1,v2: INT_LIST_TYPE; begin v1 := CONS(3, CONS(4, NIL)); v2 := CONS(5, v1); print(CAR(v1)); end; -------------------------- ** Abstraction is through specification. How to describe an abstract data type? (not by giving implementation) -information hiding principle. *** describe objects, describe effect of each operation. Objects: set of abstract values + invariant e.g.: abstract values of INT_LIST_TYPE are sequences of ints (operations: <>, <1>, x || y, first(x), tail(y) ) -something you can describe as via equations: first( || x) = tail( || x) = x tail( || <>) = <> Invariant: boolean function on abstract values e.g., only permit sequences of size<100. Operations: preconditions and postconditions: described using operations on abstract values *do them all* -work example above, formally and informally. ** Implementation: Give representation (in private part), implement operations. ---------------- package body INT_LIST is function CONS(X: INTEGER; L: INT_LIST_TYPE) return INT_LIST_TYPE is begin return new CELL'(VALUE => X, SUCC => L); end CONS; function IS_NIL(L: INT_LIST_TYPE) return BOOLEAN is begin return L = null; end IS_NIL; function CAR(L: INT_LIST_TYPE) return INTEGER is begin return L.VALUE; end CAR; function CDR(L: INT_LIST_TYPE) return INT_LIST_TYPE; begin return L.SUCC; end CAR; end INT_LIST; ---------------- *** Correctness of implementation? Describe representation + representation invariant (boolean function on representations) e.g., cells as above. lists are not circular. Abstraction function: mapping from rep to abstract values. *show this* Correctness of each operation: assume representation invariant holds assume precondition holds show post-condition holds + rep-invariant *work car and cons* Summary: Design of abstract type = overview, and package interface specification + description of abstract values + pre and post conditions for all operations Export a private type (otherwise no information hiding) constructors (like NIL, CONS) observers (like IS_NIL, CAR, CDR) mutators (like SET_CAR) mutable vs. immutable types *** More examples: ------------- type KIND is (ET,IT,RT,AT,RT,ACST); type TDKV(WHAT: KIND) is record case WHAT is when ET => E: ET_DEF; when INT_T => IT_DEF; ... end case; end record; ------------- abstractly values are a tag + abstract value for each kind of type def ------------------ package TYPE_DEF is type KIND is (ET,IT,RT,AT,RT,ACST); type TDT is private; function WHAT_KIND(X: TDT) return KIND; function MAKE_ET(E: ET_DEF) return TDT; function VAL_ET(X: TDT) return ET_DEF; -- pre: WHAT_KIND(X) = ET -- post: returns the enumeration type def -- contained in X function MAKE_IT(I: IT_DEF) return TDT; procedure VAL_IT(X: TDT) return IT_DEF; ... private type TDKV(WHAT: KIND := IT) is record case WHAT is when ENUM_T => E: ET_DEF; when INT_T => I: IT_DEF; ... end case; end record; type TDT is TDKV; end TYPE_DEF; --------------- also need a package body that implements all of these ** importing (with vs. use) *** with makes other library units (e.g. package names) visible written before package declaration introduces dependencies. *** use imports names from a package. ** Other kinds of packages: *** modules (but not abstract types) that provide name space control collection of types and subprograms (called a library in text) simply a collection of related procedures e.g., trigonometric functions data only like FORTRAN common exposed representation non-private type and operations -no encapsulation, no information hiding *** data structure manager (internal representation) *object bound together with operations single instance (vs. many in external representation) interface is operations, representation local to package (body) e.g. STACK1 on pages 290ff in text *** generic (code reuse, abstraction of common parts) has parts of definition (or implementation) abstracted and made into parameters -especially type parameters (can have no parameters) e.g., generic list package ------------ generic type T is private; package LIST is type LIST_TYPE is private; NIL: constant LIST_TYPE; function CONS(X: T; L: LIST_TYPE) return LIST_TYPE; ... end LIST; ------------ can have parameters like procedures can also have type parameters and function parameters **** instantiation ---------- package INT_LIST is new LIST(INTEGER); ---------- instantiations can share code (difficult to obtain maximum sharing, because some instantiations may manipulate objects of different sizes: e.g., stack(record1) vs. stack(char) example of abstraction principle ** Comparison of packages and block structure (see MacLennan's 7.4) with packages, can avoid indiscriminate access (hide information) a program segment can ensure access to package's names (as long as package itself is visible) names only have to be visible where needed (can control access to shared variables) access to names is by mutual consent different parts of a program can be given different access to objects (using private types) access (visibility) is decoupled from declaration Overloading --------------- package CHAR_LIST is new LIST(CHARACTER); declare use INT_LIST; use CHAR_LIST; LI: INT_LIST.LIST_TYPE; LC: CHAR_LIST.LIST_TYPE; begin LC := CONS('A',NIL); -- CHAR_LIST.CONS, CHAR_LIST.NIL LI := CONS(1,NIL); -- INT_LIST.CONS, INT_LIST.NIL end; --------------- An expression is legal if one and only one subprogram can be chosen for each operator in the expression. Operator identification relies on: name, number of parameters, types and order of actuals, names of formals (if named associations used), result type (for functions). operator identification can be accomplished in one pass! trade-off of readability vs. writeability (don't know of any principle that applies) Position-independent and default parameters (section 8.1) parameters can be associated with formals using their names, instead of positions ------- LC := CONS(L => NIL, X => 'A'); LC := CONS('A', L => NIL); ------- Similar to syntax for array, record constructors ---------- (1..5 => (1..8 => 0.0)) --matrix TABLE'(5,8,4,1, others => 0) (DAY => 4, MONTH => JULY, YEAR => 1776) ---------- parameters can have default values --------- function CONS(X: T; L: LIST_TYPE := NIL) return LIST_TYPE; -- can write CONS('A'); --------- position independent parameters and defaults complicate operator identification *feature interaction* -overloading is prohibited in subprogram declarations if introduces potential for ambiguity --------- procedure P(X: REAL; Y: REAL := 0.0); procedure P(X: REAL; Y: BOOLEAN := FALSE); --------- P(3.0) is ambiguous -complicated by other kinds of overloading (see text).