CS226 -- Day 08 -- Spring 2013 Abstract Data Types (ADTs) We define our data structures through Abstract Data Types (ADTs). One way of formalizing them is through Algebraic Specifications. Interface vs. Implementation (refer back to yesterday's example): Essentially an interface gives you only syntactic information: What are the operations and what are their types. It doesn't really tell you what those operations mean, except maybe in a comment. An implementation (class in Java) gives you all the details, and maybe a lot more details than you want to know. An Algebraic Specification (or specification for short) provides an intermediate stage, something between an interface and an implementation, something that says more and less. Consider a Counter interface: interface Counter { void increment(); int get(); } One implementation of this might start a counter at 0 and increment by one each time. Another implementation might start the counter at 2 and increment by doubling each time. If we want to say something about counters in general, something that is useful for these two specific counters but also for many others, we could specify the abstract data type for counters like this: adt Counter uses Integer defines Counter operations new: ---> Counter increment: Counter ---> Counter get: Counter ---> Integer axioms get(new()) >= 0 get(increment(c)) > get(c) The operations specify transformations of the data, and correspond to our interface operations, except that implicit object parameters become explicit instead. The axioms specify rules that must always be satisfied by the implementations of our operations. The specification says more than the interface, but it doesn't say too much like the implementation. Here's why specifications are important and useful: 1) Writing the specification forces you to think of the abstraction you're building as a whole. You have to clearly imagine the operations you want and how they will work together to achieve your goal. In this case, writing the specification is a design tool. 2) The axioms of the specification make for an excellent source of test cases. If I hand you a MysteryCounter.class file and claim that it implements the Counter specification, you can write Java code to make sure that the axioms we have are satisfied for a number of example cases. We'll make a somewhat big deal of testing in this course because it's one of the most powerful ideas in programming, and we'll see that finding good test cases can be a challenge. Having a specification helps with that. Variable (Generic) Specification - think of it as a Wrapper for any primitive type - need equality, set value, get value, create adt Variable uses Any (Any is a type that has equality defined) defines Variable operations: new: T ---> Variable get: Variable ---> T set: Variable x T ---> Variable pre-conditions: (none) axioms: get(new(t)) = t get(set(v, t)) = t Think of Variable = Integer, T = int, t some integer constant, 4 In order to create the algebraic specification for an ADT we may use some combination of these components: uses: the data types the ADT specification will rely on defines: the name of the data type being defined operations: what we can do with this type, similar to the method signatures in a Java interface pre-conditions: rules imposed on the parameters that can be used in the operations axioms: rules that specify what the results of the operations should be and how they relate to each other, providing insights for how to implement them. When an operation is limited by pre-conditions, then we say it is a partial operation and use the -/-> notation instead of ---> for a whole operation. Think of it as a way of noting that a partial operation will break for certain parameter values, whereas a whole operation works smoothly for all possible inputs. Array (Generic) Specification Let's try to create an abstract data type (ADT) specification for a bounded array in which all elements are initialized to the same value when the array is created. adt Array uses Any, Integer defines Array operations: new: Integer x T -/-> Array get: Array x Integer -/-> T set: Array x Integer x T -/-> Array size: Array ---> Integer pre-conditions: new(n,t): n > 0 get(a,i): 0 <= i < size(a) set(a,i,t): 0 <= i < size(a) axioms: (data consistency) get(new(n,t),i) = t get(set(a,i,t),i) = t get(set(a,i,t),j) = get(a,j) if i != j This one captures both of the above ones: get(set(a,i,t),j) = t if i == j, get(a,j) otherwise (size is constant) size(new(n,t)) = n size(get(a,i)) = size(a) size(set(a,i,t)) = size(a)