We shall deal with Statically, class-based, typed O.O. languages (we previously referred to them simply as "strongly typed") Many advantages to having a statically type-checked language: ** providing earlier, and usually more accurate, information on programmer errors ** eliminating the need for run-time type checks that can slow program execution and increase program size ** providing documentation on the interfaces of components (e.g., procedures, functions, and packages or modules) ** providing extra information that can be used in compiler optimizations. As a result most modern languages have static type systems Of course, the more flexible, the better! standard PASCAL: "inflexible" type system (sorting non working for arrays of different sizes) In between static and dynamic (in Theta) x:S typecase x when T(t): ... t ... when U(u): ... u ... others: ... x ... end; (in case T<:S and U<:S) Cast in Java not statically type-safe (seen in FJ) Many type problems and rigidities present in in statically typed object-oriented languages mainly because of ** conflation of type with class ** mismatch of the inheritance hierarchy with subtyping Goal: Understanding the problems, hinting at solutions. We start by reviewing some relevant concepts in OO languages Objects encapsulate both state and behavior. An OBJECT: INSTANCE VARIABLES and METHODS method invokation: message sending We assume objects are references then Sharing Semantics for assignment (as in Java and Smalltalk) CLASSES: extensible templates for creating objects, providing initial values for instance variables and the bodies for methods. All objects generated from the same class share the same methods, but contain separate copies of the instance variables. Example of notation we shall use class CellClass { x: Integer := 0; function get(): Integer is { return self.x } function set(nuVal:Integer): Void is { self.x := nuVal } function bump(): Void is { self <= set(self <= get()+1) } } "self" is "this" in Java <= to send messages In many language self is omitted We assume: instance variables are not accessible from outside of that object€™'s methods. methods are by default publicly accessible from outside of the object In many languages, a class name is ** name for the class ** name for constructor ** name for the type of the objects better not to conflate these uses ObjectType { m_1 : T_1, ... m_n : T_n } CellType = ObjectType { get: Void -> Integer; set: Integer -> Void; bump: Void -> Void } Object Types do not mention instance variable, since they are not publicly accessible! class DimCellClass { z:Integer := -1; function get(): Integer is { return self.z + 1 } function set(nuVal:Integer): Void is { self.z := nuVal - 1 } function bump(): Void is { self <= set(self <= get()+1) } } Same ObjectType dynamic method invocation: the object receiving a message is responsible for knowing which method body to execute (a mechanism enhancing flexibility) program CellExample; ... // Definitions of CellClass, DimCellClass, // and CellType omitted var c: CellType := nil; // (1) { c := new CellClass; // (2) c <= set(17); // (3) set from CellClass c <= bump(); // (4) writeln(c <= get()); // (5) c := new DimCellClass; // (6) c <= set(17); // (7) set from DimCellClass c <= bump(); // (8) writeln(c <= get()) // (9) } Subclasses: they inherit and (possibly) modify class ClrCellClass inherits CellClass modifies set { color:ColorType := blue; function getColor(): ColorType is { return self.color } function set(nuVal:int): Void is { self.x := nuVal; self.color := red } } ClrCellType = ObjectType { get: Void -> Integer; set: Integer -> Void; bump: Void -> Void; getColor: Void -> ColorType } Using "super" the set method could be { super <= set(nuVal); self.color := red } Dynamic method invocation plays an important role during inheritance K.Bruce use the "substitutability" intuition for subtyping: "We say type T is a subtype of U, written T <: U, if a value of type T can be used in any context in which a value of type U is expected." The subsumption rule introduce a sort of polymorphism in languages with subtyping: Subtype Polymorphism ClrCellType <: CellType We use Structural Subtyping Good motivations to use it instead of Nominal Subtyping (see Venneri's slides) Subtyping is a matter of types Inheritance of "implementations" >> No necessity to restrict subtyping to those relationships arising froms subclasses (uselessly restrictive) >> Besides, in reasonable languages a class may inherit from onother BUT the type of its objects not necessarily being a subtype class C { v: T1 := ...; function m(p: T2): T3 is { ... } } class SC inherits C modifies v, m { v: T1€™' := ...; function m(p: T2€™'): T3'€™ is { ... } } Which conditions over types in order to define subclasses mantaining type safeness? For method types: the restrictions (that we know) imposed by the definition of <: for record types. ObjectType { ... m : T2 -> T3 ... } <: (how we define <: in order to preserve type safety?) ObjectType { ... m : T2' -> T3' ... } But what about types of instance variables when defining subclasses? T1=T1' (not reasonable for records, but we have objects! fields are not in object types) Overloading Already met in Haskell dealing with classes Relevant notion in O.O. class Rectangle { ... function contains(pt:Point): Boolean is { ... } function contains(x,y:Integer): Boolean is { ... } } treated by Java and C++ as different names the language processor *statically* determines what method body is to be executed var r: Rectangle; pt: Point; x, y: Integer; function m(...): ... is { ... r contains(pt) ... r contains(x,y) ... } (differences) Overloaded and overridden methods overloaded methods overridden methods ----------------------------------------------- message sends | resolved statically | resolved at run time | | which class? | (tipically) same | subclass | | signature | different | same (or subtype) interaction between overloaded method names and overridden methods names (static resolution) (dynamic resolution) can result in GREAT CONFUSION class C { ... function equals(other:CType): Boolean is { ... } // equals 1 } class SC inherits C modifies equals { ... function equals(other:CType): Boolean is { ... } // equals 1 function equals(other:SCType): Boolean is { ... } // equals 2 } CType and SCType are the types of objects of C and SC Clearly SCType <: CType c and c' of type CType sc of type SCType c := new C; sc := new SC; c'€™ := new SC; c <= equals(c); c <= equals(c'); c <= equals(sc); c' <= equals(c); c'€™ <= equals(c'€™); c'€™ <= equals(sc); sc <= equals(c); sc <= equals(c'); sc <= equals(sc); Which equals method is actually executed as a result of each of the sends? * All 3 message sends to c result in the execution of method equals 1 from class C. * All 3 message sends to c'€™ result in the execution of method equals 1 in class SC. * The first two message sends to sc also result in the execution of method equals 1 from class SC. * Only the last message send, sc <= equals(sc), results in the execution of method equals 2 from class SC.