CS 342 Lecture -*- Outline -*- * Data Abstraction the key to CLU explains: type checking, scope rules, built in types, object model ** Examples (built-in types of CLU) *** int abstract values: contiguous subset of mathematical integers that includes 0 (notation digits) computers are finite => overflow possible operations: add, sub, mul, minus, div, mod, etc. each has a specification ------------ add = proc(a,b: int) returns(int) signals(overflow) % EFFECT: if a+b is in represented set of integers, return a+b; % otherwise signal overflow. from_to = iter(from, to: int) yields(int) % EFFECT: yield from, from+1, ..., to parse = proc(s: string) returns(int) signals(bad_format,overflow) % EFFECT: if s has the form of a base 10 integer literal, % if the integer coded by s is in the represeted set, % then return the integer coded by s, otherwise signal overflow; % else if s has the wrong form, signal bad_format. lt = proc(a,b: int) returns(bool) % EFFECT: return a < b equal = proc(a,b: int) returns(bool) % EFFECT: return true iff a is the same as b copy = proc(a: int) returns(int) % EFFECT: return a -------------------- *** array a type generator: array[t] is a type for each type t (think of t as fixed) objects are mutable abstract value of a typical array[t]: an integer "low" (the low bound), and a tuple of objects of type t (the elements), Notation is [5: x,y,z] for an array with low 5 and tuple , x is at the "low" end (the 5th element) and z is at the "high" end (the 7th element) Arrays are also finite, but exceeding the maximum size of an array is ignored below Note: there is no notion of undefined element. -------------- create = proc(lb: int) returns(array[t]) % EFFECT: returns a new, empty array with low bound lb (The phrase "a new array" means an array that is different from all others.) fill = proc(lb, cnt: int, elem: t) returns(array[t]) signals(negative_size) % EFFECT: if cnt < 0, signal negative_size; otherwise return a new % array with low bound lb and a tuple of size cnt, % where each element of the tuple is elem. addh = proc(a: array[t], elem: t) % MODIFIES: a % EFFECT: extend a by 1 on the high end and makes elem the new element fetch = proc(a: array[t], i: int) returns(t) signals(bounds) % EFFECT: if i is out of bounds (i.e., less than the low bound of a % or greater than the size of a's tuple plus the low bound % minus one), then signal bounds; otherwise return % the ith element of a. store = proc(a: array[t], i: int, elem: t) signals(bounds) % EFFECT: if i is out of bounds, signal bounds; otherwise make elem % the ith element of a. elements = iter(a: array[t]) yields(t) % EFFECT: yields the elements of a's tuple, in order from low to high equal = proc(a,b: array[t]) returns(bool) % EFFECT: return true iff a and b are the same object -------------- Syntactic sugars suppose a,b: array[char] a := array[char]$fill(0,3,'\t') a[3] := 'c' => array[char]$store(a,3,'c') a[3] := a[2] => array[char]$store(a,3,array[char]$fetch(a,2)) a = b => array[char]$equal(a,b) a[3] = b[3] => array[char]$equal(array[char]$fetch(a,3), array[char]$fetch(b,3)) array[char]$[3: 'a','b','\n'] sugar for constructing object with abs. value [3: 'a','b','\n'] suppose s: record[x,y: real] s := record[x,y: real]${x: 3.4, y: 2.0} s.x := 3.0e2 => record[x,y: real]$set_x(s, 3.0e2) s.x := s.y => record[x,y: real]$set_x(s, struct[x,y: real]$get_y(s)) These sugars also work for other types a: array[char] := array[char]$fill(0,3,'\t') a.low := 1 => array[char]$set_low(a,1) ** Clusters (modules that implement data abstractions) cluster (see interval example) define rep type (data structure), implement operations *** operation export and hiding "is" list, exports operations ones not listed are hidden rep type is not equal to the abstract type. *** representation (rep) data structure used to represent the objects (a type) e.g., for intervals, a record that holds two ints e.g., for stack of ints, array[int] representation invariant: property of representing objects (for intervals, that lower <= upper) What does a rep object represent? abstraction function: map from rep to abstract values, defined whenever rep invariant is true e.g., for intervals r represents [r.lower, r.upper] *** security of data abstraction (langauge enforcement of data abtraction) goals: make sure modules outside cluster cannot "discover" rep help preserve invariant when objects are sent out from cluster inductive proof: establish rep invariant when create an object of rep type. when pass out, want it to be protected, so change type using "up" (wrap object in shield) ------------ % create of interval create = proc(l,u: int) returns(interval) signals(empty) % EFFECT: if l <= u, then return [l,u], otherwise signal empty if (l <= u) then return(up(rep${lower:l, upper:u})) else signal empty end end create ------------ when get object as argument, melt the shield using "down" can assume rep invariant holds when mutate an object, re-establish invariant before passing out again finally re-wrap using up before passing out **** up wraps object in "shield" wrapping can happen at several levels of abstraction sorted list { list { array } } **** down melts shield **** cvt shorthand (down on the way in, up on way out) **** rep type not equal to abstract type even inside the cluster! ensures information hiding cannot change type of an expression (no coercions) except in cluster that implements that type