Mixins and Traits
Total Page:16
File Type:pdf, Size:1020Kb
◦ ◦◦◦ TECHNISCHE UNIVERSITAT¨ MUNCHEN¨ ◦◦◦◦ ◦ ◦ ◦◦◦ ◦◦◦◦ ¨ ¨ ◦ ◦◦ FAKULTAT FUR INFORMATIK Programming Languages Mixins and Traits Dr. Michael Petter Winter 2016/17 What advanced techiques are there besides multiple implementation inheritance? Outline Design Problems Cons of Implementation Inheritance 1 Inheritance vs Aggregation 1 2 (De-)Composition Problems Lack of finegrained Control 2 Inappropriate Hierarchies Inheritance in Detail A Focus on Traits 1 A Model for single inheritance 1 2 Inheritance Calculus with Separation of Composition and Inheritance Expressions Modeling 2 3 Modeling Mixins Trait Calculus Mixins in Languages Traits in Languages 1 (Virtual) Extension Methods 1 Simulating Mixins 2 Squeak 2 Native Mixins Reusability ≡ Inheritance? Codesharing in Object Oriented Systems is often inheritance-centric. Inheritance itself comes in different flavours: I single inheritance I multiple inheritance All flavours of inheritance tackle problems of decomposition and composition The Adventure Game Door ShortDoor LockedDoor canPass(Person p) canOpen(Person p) ? ShortLockedDoor canOpen(Person p) canPass(Person p) The Adventure Game Door <interface>Doorlike canPass(Person p) canOpen(Person p) Short canPass(Person p) Locked canOpen(Person p) ShortLockedDoor ! Aggregation & S.-Inheritance Door must explicitely provide canOpen(Person p) chaining canPass(Person p) Doorlike must anticipate wrappers ) Multiple Inheritance X The Wrapper FileStream SocketStream read() read() write() write() ? SynchRW acquireLock() releaseLock() ! Inappropriate Hierarchies Cannot inherit from both in turn with Multiple Inheritance (Many-to-One instead of One-to-Many Relation) Creating individual special wrapping Classes duplicates acquire/release code The Wrapper – Aggregation Solution Stream read() write() FileStream SocketStream read() read() write() write() SynchRW read() ! Aggregation write() acquireLock() Undoes specialization releaseLock() Needs common ancestor The Wrapper – Multiple Inheritance Solution FileStream SynchRW SocketStream read() acquireLock() read() write() releaseLock() write() SynchedFileStream SynchedSocketStream read() read() write() write() ! Duplication With multiple inheritance, read/write Code is essentially identical but duplicated for each particular wrapper Fragility SynchRW acquireLock() releaseLock() FileStream SocketStream read() read() write() write() SynchedFileStream SynchedSocketStream ! Inappropriate Hierarchies Implemented methods (acquireLock/releaseLock) to high (De-)Composition Problems All the problems of Duplication Fragility Lack of fine-grained control are centered around the question “How do I distribute functionality over a hierarchy” functional (de-)composition Classes and Methods The building blocks for classes are a countable set of method names N a countable set of method bodies B Classes map names to elements from the flat lattice B (called bindings), consisting of: > attribute offsets 2 N+ method bodies 2 or classes 2 C B n n m m c c ? abstract 1 2 1 ::: 2 1 2 > in conflict and the partial order ? v b v > for each b 2 B ? Definition (Abstract Class 2 C) A function c : N 7! B with at least one abstract image is called abstract class. Definition (Interface and Class) An abstract class c is called (with pre beeing the preimage) interface iff 8n2pre(c) : c(n) = ?. (concrete) class iff 8n2pre(c) : ? @ c(n) @ >. Computing with Classes and Methods Definition (Family of classes C) We call the set of all maps from names to bindings the family of abstract classes C := N 7! B. Several possibilites for composing maps C C: the symmetric join t, defined componentwise: 8 >b2 if b1 = ? or n2 = pre(c1) > <b1 if b2 = ? or n2 = pre(c2) (c1 t c2)(n) = b1 t b2 = where bi = ci(n) >b2 if b1 = b2 > :> otherwise in contrast, the asymmetric join t, defined componentwise: ( c1(n) if n 2 pre(c1) (c1 t c2)(n) = c2(n) otherwise Example: Smalltalk-Inheritance Smalltalk inheritance childrens methods dominate parents methods is the archetype for inheritance in mainstream languages like Java or C# inheriting smalltalk-style establishes a reference to the parent Definition (Smalltalk inheritance (.)) Smalltalk inheritance is the binary operator . : C × C 7! C, definied by c1 . c2 = fsuper 7! c2g t (c1 t c2) Example: Doors Door = fcanP ass 7! ?; canOpen 7! ?g LockedDoor = fcanOpen 7! 0x4204711g . Door = fsuper 7! Doorg t (fcanOpen 7! 0x4204711g t Door) = fsuper 7! Door; canOpen 7! 0x4204711; canP ass 7! ?g Excursion: Beta-Inheritance In Beta-style inheritance the design goal is to provide security wrt. replacement of a method by a different method. methods in parents dominate methods in subclass the keyword inner explicitely delegates control to the subclass Definition (Beta inheritance (/)) Beta inheritance is the binary operator / : C × C 7! C, definied by c1 / c2 = finner 7! c1g t (c2 t c1) Example (equivalent syntax): class Person{ String name= "Axel Simon"; public String toString(){ return name+inner.toString();}; }; class Graduate extends Person{ public extension String toString(){ return ", Ph.D.";}; }; Extension: Attributes Remark: Modelling attributes is not in our main focus. Anyway, most mainstream languages nowadays are designed so that attributes are not overwritten: Definition (Mainstream inheritance (.0)) The extended mainstream inheritance .0 : C × C 7! C binds attribute n statically and without overwriting: 8 >c2 if n = super > + 0 <> if n 2 pre(c1) ^ c2(n) 2 (N [ >) (c1 . c2)(n) = >c1(n) if n 2 pre(c1) > :c2(n) otherwise So what do we really want? Adventure Game with Code Duplication Door ShortDoor LockedDoor canPass(Person p) canOpen(Person p) ShortLockedDoor canOpen(Person p) canPass(Person p) Adventure Game with Mixins <mixin>Locked Door canOpen(Person p) canOpen(Person p) canPass(Person p) <mixin>Short mixin canPass(Person p) compose ShortLockedDoor Adventure Game with Mixins class Door{ boolean canOpen(Person p){ return true;}; boolean canPass(Person p){ returnp.size()< 210;}; } mixin Locked{ boolean canOpen(Person p){ if(!p.hasItem(key)) return false; else return super.canOpen(p); } } mixin Short{ boolean canPass(Person p){ if(p.height()>1) return false; else return super.canPass(p); } } class ShortDoor= Short(Door); class LockedDoor= Locked(Door); mixin ShortLocked= Short o Locked; class ShortLockedDoor= Short(Locked(Door)); class ShortLockedDoor2= ShortLocked(Door); Back to the blackboard! Abstract model for Mixins A Mixin is a unary second order type expression. In principle it is a curried version of the Smalltalk-style inheritance operator. In certain languages, programmers can create such mixin operators: Definition (Mixin) The mixin constructor mixin : C 7! (C 7! C) is a unary class function, creating a unary class operator, defined by: mixin(c) = λx : c . x ! Note: Mixins can also be composed ◦: Example: Doors Locked = fcanOpen 7! 0x1234g Short = fcanP ass 7! 0x4711g Composed = mixin(Short) ◦ (mixin(Locked)) = λx : Short . (Locked . x) = λx : fsuper 7! Lockedg t (fcanOpen 7! 0x1234; canP ass 7! 0x4711g . x) Wrapper with Mixins FileStream SocketStream read() read() write() write() mixin <mixin>SynchRW acquireLock() releaseLock() mixin SynchedFileStream SynchedSocketStream read() read() write() write() Mixins on Implementation Level Short class Door{ canPass() boolean canOpen(Person p)... boolean canPass(Person p)... super } mixin Locked{ Locked ... boolean canOpen(Person p)... } canOpen() mixin Short{ super boolean canPass(Person p)... } ... class ShortDoor Door = Short(Door); class ShortLockedDoor = Short(Locked(Door)); ... ! non-static super-References ShortDoor d dynamic dispatching without = new ShortLockedDoor(); precomputed virtual table Surely multiple inheritance is powerful enough to simulate mixins? Simulating Mixins in C++ template<class Super> class SyncRW: public Super { public: virtual int read(){ acquireLock(); int result= Super::read(); releaseLock(); return result; }; virtual void write(int n){ acquireLock(); Super::write(n); releaseLock(); }; // ... acquireLock & releaseLock }; Simulating Mixins in C++ template<class Super> class LogOpenClose: public Super { public: virtual void open(){ Super::open(); log("opened"); }; virtual void close(){ Super::close(); log("closed"); }; protected: virtual void log(char*s) { ... }; }; class MyDocument: public SyncRW<LogOpenClose<Document>> {}; True Mixins vs. C++ Mixins True Mixins super natively supported C++ Mixins Mixins as Template do not offer composite mixins Mixins reduced to templated superclasses C++ Type system not modular Can be seen as coding Mixins have to stay source pattern code Hassle-free simplified version of multiple inheritance Common properties of Mixins Linearization is necessary Exact sequence of Mixins is relevant Ok, ok, show me a language with native mixins! Ruby module Short def canPass(p) class Person p.size< 160 and super(p) attr_accessor :size end def initialize end @size= 160 module Locked end def canOpen(p) def hasKey p.hasKey() and super(p) true end end end end class Door class ShortLockedDoor< Door def canOpen(p) include Short true include Locked end end def canPass(person) person.size< 210 p= Person.new end d= ShortLockedDoor.new end putsd.canPass(p) Ruby class Door module ShortLocked def canOpen(p) include Short true include Locked end end def canPass(person) class Person person.size< 210 attr_accessor :size end def initialize end @size= 160 module Short end def canPass(p) def hasKey p.size< 160 and super(p) true end end end end module Locked def canOpen(p) p= Person.new p.hasKey() and super(p) d= Door.new end d.extend ShortLocked