Lecture 12: Abstract data types Algebras Abstract data types Encapsulation and information hiding Parameterization Algebras Definition Which kinds of data do we want to discuss? What can we do with it? An algebra A is a tuple A = (D 1,,D m, f 1,,f n ) where every D i is a set (called domain) and every f j is a function f j between domains (called operation), i.e f j :X 1 X k Y where X 1,,X k,y {D 1,,D m. 1
Algebras vs programs Unfortunately programs have much higher complexity. Why? A program does not only describe the abstract functionality but first of all how it is realized (= implemented), often in detail. One problem: Other parts of the program may exploit implementation details that are not part of the functionality. Correctness depends on arbitrary random assumptions Every little detail may influence other parts of the program Abstract data types Abstract data types (ADT:s) is an attempt to apply algebraic concepts to programming languages: Characteristics of data and operations are described on an abstract level; nothing about implementation Other parts of the program cannot use implementation details Other units can only use the specified abstract (invariable) properties; not implementation-related details (which may change) This is an example of data abstraction 2
Encapsulation and information hiding To reach these goals an ADT must fulfil two conditions: Encapsulation The definition of an ADT specifies its name and its operations in one single syntactic unit (not scattered in several units). Other program units may refer to this definition (only) to use the ADT. Information hiding The internal representation of data is not accessible for other program units. Example of a formal specification Algebraic specifikation of ADT stack ADT stack is elem + boolean + operations: empty: stack push: stack x elem stack, pop: stack stack, top: stack elem, isempty: stack boolean equations: isempty(empty) = true, isempty(push(s, e)) = false, pop(push(s, e)) = s, top(push(s, e)) = e, pop(empty) = error, top(empty) = error 3
Formal specification (2) What operations? Constructors ( returns an object of the actual type) ( true constructors, modifiers, destructors) Inspectors (examines the inner structure) (selectors, predicates) Comparators (compares equality, if not in the language) What equations? Rule of thumb: One for each combination constructor - inspector One for each combination constructor - destructor Implementation Implementation of an ADT: Interface - definition of types and operations on types, available for the clients Internal representation of the objects, implementation of operations. Invisible for the clients For maximal flexibility: Need to be able to separate definition (interface) and implementation Rich & restrictive interface, completeness (cf DoA) Different languages give more or less support Only ADT:s, or general modules? Information hiding? Separation of definition & specification? How can we connect modules? Freedom fr block struct.? Parameterized modules? 4
Example: stack in Java import java.io.*; class stack { private Vector s = new Vector(); public void push(elem e) { s.addelement(e); public void pop() { s.removeelementat(s.size() - 1); public elem top() { return s.lastelement(); public boolean isempty() { return s.size() = 0; Parameterized ADT:s If an ADT builds upon other, simpler ADT:s we often wish to be able to create different instances using parameters stack(integer), stack(tree), stack(stack(tree)), sortedlist(integer), sortedlist(string), N.B: These parameters are data types! Problem: In some languages (e.g old versions of Java) it is not possible to explicitly create different instances We cannot distinguish between sortedlist(integer) and sortedlist(string) 5
exception EmptyStack Example: stack in ML abstype a stack = Stack of a list with val empty = Stack [] fun push (Stack s, e) = Stack (e::s) fun pop (Stack []) = raise EmptyStack pop (Stack (_::xs)) = Stack xs fun top (Stack []) = raise EmptyStack top (Stack (x::_)) = x fun isempty (Stack s) = s=[] val stack1 = push (empty, 1); > val stack1 = -:int stack val stack2 = push(push(empty,(1, Otto )),(2, Olga )); > val stack2 = -:(int*string)stack Example: MyStack in HASKELL module MyStack ( MyStack, empty, push, pop, top, isempty, smallest ) where newtype (Ord a) => MyStack a = StackOf [a] empty :: (Ord a) => MyStack a empty = StackOf [] push (StackOf xs) x = StackOf (x : xs) pop (StackOf (x:xs)) = StackOf xs top (StackOf (x:xs)) = x isempty (StackOf xs) = xs == [] smallest (StackOf (x:xs)) = getsmallest x xs getsmallest x [] = x getsmallest x (y:ys) x < y = getsmallest x ys otherwise = getsmallest y ys Not visible! visible The parameter must have an order relation `<` istack :: MyStack Integer sstack :: MyStack String 6
Modules in MLan example of encapsulation ML has very powerful, general (and therefore difficult to understand?) modules: > Structures: package with concrete definitions / implementations > Signatures: types of a structure > Functors: parameterized structures Example: stack in ML again signature StackItem = sig type item val isequal: item -> item -> bool signature Stack = sig type item type stack exception EmptyStack val empty: stack val push: (stack * item) -> stack....and so on functor MkStack(Item:StackItem):> Stack = struct type myitem = Item.item type stack = myitem list exception EmptyStack val empty = [] fun push (s,e) = e::s...etc 7
Example: stack in ML again (2) structure Pair:StackItem = struct type item = int * string fun isequal (i1,s1) (i1,s2) = i1=i2 andalso s1=s2 structure PairStack = MkStack(Pair); val mystack = PairStack.push (PairStack.empty, (3, Haskell )); 8