Com S 541 Lecture -*- Outline -*- * data abstraction (6.4) data abstraction = a specified interface for operating on instances = a specified set of operations that work on hidden data hides data structures and algorithms synonyms: datatype, type ** eight ways to organize a data abstraction (6.4.1) ------------------------------------------ PROPERTIES OF DATA ABSTRACTIONS ^ ^ Bundled| / Stateful | / | / | / | / not | /not Stateful Bundled|/ |-----------------------------> Not Secure Secure Data abstraction is: secure, if encapsulation can be enforced by the language open, if it is not secure stateful, if it must be implemented using explicit state stateless, if it is not stateful bundled, if it contains just objects unbundled, if it has both values and ops ------------------------------------------ Q: Who enforces encapsulation if the language doesn't? Tools, or the programmer ------------------------------------------ BUNDLING Unbundled data abstraction = ADT Can be secured by Examples: CLU, Ada 83 Bundled data abstraction = Class (spec) Reynolds's term: procedural data abstr. Operations (methods) inside objects ------------------------------------------ ... using a key (as with NewName) Q: In an unbundled abstraction, can the values make use of explicit state? Yes ** variations on a stack (6.4.2) Look at the files Stack*.oz: Note the tests for file F.oz are in FTest.oz The tests are important for showing how the specifications differ (these are different abstractions, except for the first 2, not different implementations of the same abstraction) *** StackOpenDeclUnbun.oz -- open, declarative, unbundled ------------------------------------------ % $RCSfile: StackOpenDeclUnbun.oz,v $ declare local fun {NewStack} nil end fun {Push S E} E|S end fun {Pop S ?E} case S of X|S1 then E=X S1 end end fun {IsEmpty S} S==nil end in Stack=stack(new:NewStack push:Push pop:Pop isEmpty:IsEmpty) end % $RCSfile: StackOpenDeclUnbunTest.oz,v $ \insert 'StackOpenDeclUnbun.oz' \insert 'StackDeclUnbunTest.oz' % $RCSfile: StackDeclUnbunTest.oz,v $ \insert 'Test.oz' declare S1 S2 S3 S4 S5 X Y S1 = {Stack.new} {Test {Stack.isEmpty S1} '==' true} S2 = {Stack.push S1 3} {Test {Stack.isEmpty S2} '==' false} S3 = {Stack.push S2 4} {Test {Stack.isEmpty S3} '==' false} S4 = {Stack.pop S3 X} {Test X '==' 4} {Test {Stack.isEmpty S4} '==' false} S5 = {Stack.pop S4 Y} {Test Y '==' 3} {Test {Stack.isEmpty S5} '==' true} ------------------------------------------ Q: What does NewStack do? Push? Pop? IsEmpty? Q: Is this persistent? Yes Q: Why is it open? There's nothing that stops clients from using the list directly Q: Why does the test need all those variables? Because the test is keeping the state, this is declarative *** StackSecureDeclUnbun.oz -- secure, declarative, unbundled ------------------------------------------ % $RCSfile: StackSecureDeclUnbun.oz,v $ \insert 'NewWrapper.oz' declare local Wrap Unwrap {NewWrapper Wrap Unwrap} fun {NewStack} {Wrap nil} end fun {Push S E} {Wrap E|{Unwrap S}} end fun {Pop S ?E} case {Unwrap S} of X|S1 then E=X {Wrap S1} end end fun {IsEmpty S} {Unwrap S}==nil end in Stack=stack(new:NewStack push:Push pop:Pop isEmpty:IsEmpty) end % $RCSfile: StackSecureDeclUnbunTest.oz,v $ \insert 'StackSecureDeclUnbun.oz' \insert 'StackDeclUnbunTest.oz' ------------------------------------------ Q: How does this differ from the previous one? Q: When is Wrap used? Unwrap? *** StackSecureDeclBun.oz -- secure, declarative, bundled ------------------------------------------ % $RCSfile: StackSecureDeclBun.oz,v $ \insert 'NewWrapper.oz' declare local fun {StackObject S} fun {Push E} {StackObject E|S} end fun {Pop ?E} case S of X|S1 then E=X {StackObject S1} end end fun {IsEmpty} S==nil end in stack(push:Push pop:Pop isEmpty:IsEmpty) end in fun {NewStack} {StackObject nil} end end % $RCSfile: StackSecureDeclBunTest.oz,v $ \insert 'Test.oz' \insert 'StackSecureDeclBun.oz' declare S1 S2 S3 S4 S5 X Y S1 = {NewStack} {Test {S1.isEmpty} '==' true} S2 = {S1.push 3} {Test {S2.isEmpty} '==' false} S3 = {S2.push 4} {Test {S3.isEmpty} '==' false} S4 = {S3.pop X} {Test X '==' 4} {Test {S4.isEmpty} '==' false} S5 = {S4.pop Y} {Test Y '==' 3} {Test {S5.isEmpty} '==' true} ------------------------------------------ Q: What does NewStack do? Push? Pop? IsEmpty? Q: Is this persistent? Yes Q: Why are the tests different? Because the interface is different, the stack contains the ops Q: Is this OO? yes, but still declarative Q: How is this secure without using wrappers? From lexical scoping See Malcolm P. Atkinson and Ronald Morrison, "Procedures as Persistent Data Objects", ACM TOPLAS, 7(4):539-559, Oct. 1985. *** StackSecureStateBun.oz -- secure, stateful, bundled ------------------------------------------ % $RCSfile: StackSecureStateBun.oz,v $ declare fun {NewStack} C={NewCell nil} proc {Push E} C:=E|@C end fun {Pop} case @C of X|S1 then C:=S1 X end end fun {IsEmpty} @C==nil end in stack(push:Push pop:Pop isEmpty:IsEmpty) end % $RCSfile: StackSecureStateBunTest.oz,v $ \insert 'Test.oz' \insert 'StackSecureStateBun.oz' \insert 'StackStateBunTest.oz' % $RCSfile: StackStateBunTest.oz,v $ \insert 'Test.oz' declare S X Y S = {NewStack} {Test {S.isEmpty} '==' true} {S.push 3} {Test {S.isEmpty} '==' false} {S.push 4} {Test {S.isEmpty} '==' false} {S.pop X} {Test X '==' 4} {Test {S.isEmpty} '==' false} {S.pop Y} {Test Y '==' 3} {Test {S.isEmpty} '==' true} ------------------------------------------ Q: What's different about Push and Pop now? Q: How can you see this is stateful? Q: How does the statefulness get reflected in the interface? Q: How is the object represented? Q: Why is it secure? *** StackSecureStateBunProcDispatch.oz -- secure, stateful, bundled ------------------------------------------ % $RCSfile: StackSecureStateBunProcDispatch.oz,v $ declare fun {NewStack} C={NewCell nil} proc {Push E} C:=E|@C end fun {Pop} case @C of X|S1 then C:=S1 X end end fun {IsEmpty} @C==nil end in stack(push:Push pop:Pop isEmpty:IsEmpty) end % $RCSfile: StackSecureStateBunProcDispatchTest.oz,v $ \insert 'Test.oz' \insert 'StackSecureStateBunProcDispatch.oz' \insert 'StackStateBunTest.oz' ------------------------------------------ Q: How is the object represented? Q: How does that differ from the previous version? Q: Why do the same tests work? Q: What are the efficiency differences? This version is used in chapter 7 for OOP Q: How does this differ from the stateless version? *** StackSecureStateUnbun.oz -- secure, stateful, unbundled ------------------------------------------ % File StackSecureStateUnbun.oz \insert 'NewWrapper.oz' declare local Wrap Unwrap {NewWrapper Wrap Unwrap} fun {NewStack} {Wrap {NewCell nil}} end proc {Push S E} C={Unwrap S} in C:=E|@C end fun {Pop S} C={Unwrap S} in case @C of X|S1 then C:=S1 X end end fun {IsEmpty S} @{Unwrap S}==nil end in Stack=stack(new:NewStack push:Push pop:Pop isEmpty:IsEmpty) end % $RCSfile: StackSecureStateUnbunTest.oz,v $ \insert 'Test.oz' \insert 'StackSecureStateUnbun.oz' declare S X Y S = {Stack.new} {Test {Stack.isEmpty S} '==' true} {Stack.push S 3} {Test {Stack.isEmpty S} '==' false} {Stack.push S 4} {Test {Stack.isEmpty S} '==' false} X = {Stack.pop S} {Test X '==' 4} {Test {Stack.isEmpty S} '==' false} Y = {Stack.pop S} {Test Y '==' 3} {Test {Stack.isEmpty S} '==' true} ------------------------------------------ Q: How is this like the secure, declarative, unbundled version? Q: How does it differ from that? Q: How does it differ from the bundled stateful versions? This version is "little used" but "deserves to be more widely known" ** Polymorphism (6.4.3) ------------------------------------------ POLYMORPHISM def: an operation is *polymorphic* iff it works correctly for arguments of different implementation types. ------------------------------------------ Q: What's an implementation type? a class in OOP, as in Java a particular realization of an ADT (as in CLU, Ada 83) Q: What about in bundled data abstractions where the 'argument' is implicit? Still considered polymorphic Q: What are the advantages? reuse concentrating responsibility (knowledge of algorithm) in one piece of code analogy: different kinds of restaurants and "sell_meal" method Q: Can each organization of ADTs be polymorphic? Yes, the interface stays the same, i.e., there can be multiple implementations. Key is that these organizations allow tests that work on interfaces. Q: What is required to do polymorphism in the ADT (unbundled) style? first-class modules (records and first-class procedures) *** Collection type abstraction of stacks and other collections put and get operations **** unbundled, implemented by stacks ------------------------------------------ UNBUNDLED (ADT) COLLECTION TYPE % $RCSfile: CollectionSecureStateUnbun.oz,v $ \insert 'NewWrapper.oz' \insert 'StackSecureStateUnbun.oz' declare local Wrap Unwrap {NewWrapper Wrap Unwrap} fun {NewCollection} {Wrap {Stack.new}} end proc {Put C X} S={Unwrap C} in {Stack.push S X} end fun {Get C} S={Unwrap C} in {Stack.pop S} end fun {IsEmpty C} {Stack.isEmpty {Unwrap C}} end in Collection=collection(new:NewCollection put:Put get: Get isEmpty:IsEmpty) end % $RCSfile: CollectionSecureStateUnbunTest.oz,v $ \insert 'Test.oz' \insert 'CollectionSecureStateUnbun.oz' declare C X Y C = {Collection.new} {Test {Collection.isEmpty C} '==' true} {Collection.put C 3} {Test {Collection.isEmpty C} '==' false} {Collection.put C 4} {Test {Collection.isEmpty C} '==' false} X = {Collection.get C} {Test X '==' 4} {Test {Collection.isEmpty C} '==' false} Y = {Collection.get C} {Test Y '==' 3} {Test {Collection.isEmpty C} '==' true} ------------------------------------------ Q: Is this stateful? Q: How would you implement a version of collection that has the same interface, but that does sets instead of stacks? Q: How would you work with both stacks and sets with this interface? **** bundled, implemented by stack ------------------------------------------ BUNDLED (OBJECT) COLLECTION TYPE % $RCSfile: CollectionSecureStateBun.oz,v $ \insert 'StackSecureStateBun.oz' declare fun {NewCollection} S = {NewStack} proc {Put X} {S.push X} end fun {Get} {S.pop} end fun {IsEmpty} {S.isEmpty} end in collection(put:Put get:Get isEmpty:IsEmpty) end % $RCSfile: CollectionSecureStateBunTest.oz,v $ \insert 'Test.oz' \insert 'CollectionSecureStateBun.oz' declare C = {NewCollection} {Test {C.isEmpty} '==' true} {C.put 3} {Test {C.isEmpty} '==' false} {C.put 4} {Test {C.isEmpty} '==' false} X={C.get} {Test X '==' 4} {Test {C.isEmpty} '==' false} Y={C.get} {Test Y '==' 3} {Test {C.isEmpty} '==' true} ------------------------------------------ Q: What is the difference? The object itself has the operations Q: How would you implement a version of collection that has the same interface, but that does sets instead of stacks? Q: How would you work with both stacks and sets with this interface? *** Binary methods Q: How would you make a union operation on collections in these 2 styles? 2 ways for the ADT style: use internal representation (unwrap) on both arguments (efficient, nonpolymorphic) use only the external protocol (less efficient, polymorphic) In bundled (OO) style, have to use external rep on second argument Consider a union operation for collections Advantages of the ADT style: - can access representation of both arguments in binary method so can be more efficient - needed for basic primitives - easy to add a new operation Advantages of the OOP style: - polymorphism is implicit ("for free"), no need for first-class modules - easy to add a new implementation (variant), For contrast between styles of programming data abstractions see William R. Cook, "Object-Oriented Programming Versus Abstract Data Types", in J. W. de Bakker, W. P. de Roever, G. Rozenberg (eds.), Foundations of Object-Oriented Languages, REX School/Workshop, Noordwijkerhout, The Netherlands, May/June 1990, pages 151-178. Volume 489 of LNCS, Springer-Verlag, NY, 1991. http://www.cs.utexas.edu/~wcook/papers/OOPvsADT/CookOOPvsADT90.pdf For binary methods, see Kim Bruce, Luca Cardelli, Giuseppe Castagna, The Hopkins Object Group, Gary T. Leavens, and Benjamin Pierce, "On Binary Methods", Theory and Practice of Object Systems, 1(3):221-242, John, Wiley and Sons, Inc., NY, 1995. *** ad hoc vs. parametric ------------------------------------------ AD HOC vs. PARAMETRIC POLYMORPHISM In *ad-hoc* polymorphism, different code is run for different argument types. E.g., (static) operator overloading In *parametric* or *universal* polymorphism, the code is same for all types. ------------------------------------------ ** parameter passing (6.4.4) ------------------------------------------ PARAMETER PASSING MECHANISMS action at call at return mechanism | copy: copy: Langs ===========|============================= value value - Java reference address - Oz value-result address result Ada 83 value result address result Ada out name address - Scala of thunk need address - Haskell of thunk ------------------------------------------ *** call by reference If identifiers denote cells, can write Swap ------------------------------------------ CALL BY REFERENCE For actuals pass ================================= identifiers address id denotes other expressions address of temp holding value Makes aliases between: actual ids and formals Found in: Fortran, Pascal (var params), C++ (& params), Oz ------------------------------------------ % $RCSfile: Swap.oz,v $ declare proc {Swap X Y} Temp in Temp=@X X:=@Y Y:=Temp end *** call by value Even if identifiers denote cells, can't write Swap ------------------------------------------ CALL BY VALUE For actuals pass ================================= all expressions value No aliasing, each formal has own location Found in: C Java, C#, C++, ------------------------------------------ Q: If you have call by reference, how to simulate call by value? make a new cell for each formal, assign actual's value to it Q: How would you write an increment procedure, Inc, to act like it was called by value in Oz? *** call by value-result, by result ------------------------------------------ CALL BY VALUE-RESULT For actuals pass ================================= identifiers address id denotes other expressions address of temp holding value On return, copy final value from formal back to actual No aliasing, each formal has own location But can have interference at end Found in: Algol W, Ada, ------------------------------------------ Q: What if actual arguments are repeated? Then order of copying back matters Q: What should call by result do? In Ada: in, out, in out Q: If you have call by reference, how to simulate call by value-result? make a new cell for each formal, assign actual's value to it, at the end, assign the final formal's value back to the actual Q: How would you write an increment procedure, Inc, to act like it was called by value-result in Oz? *** call by name ------------------------------------------ CALL BY NAME For actuals pass ================================= literals value procedure literals value (= closure) other expressions address of thunk (frozen actual) Def: a *thunk* is a prameterless function Can have interference throughout Found in: Algol 60 Scala ------------------------------------------ Q: Do you need macros if you have call by name? No Q: How would you write an increment procedure, Inc, to act like it was called by name in Oz? *** call by need ------------------------------------------ CALL BY NEED For actuals pass ================================= literals value procedure literals value (= closure) other expressions address of thunk (frozen actual) Can have interference throughout Only evaluate thunk once, save value when used Found in: Haskell Oz ------------------------------------------ Q: How would you write an increment procedure, Inc, to act like it was called by name in Oz? Show a non-strict example. ** Revocable capabilities (6.4.5) Q: What's a capability? ------------------------------------------ REVOCABLE CAPABILITIES Capability + method to revoke it % $RCSfile: Revocable.oz,v $ declare proc {Revocable Obj ?R ?RObj} C={NewCell Obj} in proc {R} C := proc{$ M} raise invokedError end end end proc {RObj M} {@C M} end end ------------------------------------------ Q: What does RObj do? forwards messages to @C Q: What does R do? It actually revokes it. Q: How does it work? By replacing the cell's contents with a method that gives an error. Q: Why did we need state for this?