340 likes | 355 Views
A talk by Hongwei Xi from Boston University on combining object-oriented programming and typed functional programming, discussing the challenges and proposing solutions.
E N D
Unifying Object-Oriented Programming with Typed Functional Programming Hongwei Xi Boston University
Talk Overview • Motivation for this work • Some difficult issues in designing a type system to support object-oriented programming • An approach to implementing objects through the use of guarded recursive datatype constructors
An Example in Scheme (define (newpair x y) (define (dispatch msg) (cond ((eq? msg ‘getfst) x) ((eq? msg ‘getsnd) y) ((eq? msg ‘setfst) (lambda (x1) (set! x x1))) ((eq? msg ‘setsnd) (lambda (y1) (set! y y1)))))dispatch)
Motivation • Support object-oriented programming in a typed functional programming language • Take a CLOS-like approach to object-oriented programming • Following Smalltalk, objects are not treated as records in this approach
Difficulties in Typing Objects • All existing approaches to typing objects that we know contain some major deficiencies • The programmer needs to program around the deficiencies • Run-time type checks are employed to overcome the deficiencies • Some deficiencies exist even in a bare minimum core that supports object-oriented programming
A Problem with Return Types class ObjClass {…virtual copy: ObjClass; …} class StringClass inherits ObjClass {… copy = … …}
A Problem with Binary Methods class eqClass {virtual equal (other: eqClass): bool;…} class aEqClass inherits eqClass { … } class bEqClass inherits eqClass { … }
A Puzzling Example class objClass {…virtual copy: objClass; …} class int1Class inherits objClass{…copy = ... ;…} class int2Class inherits int1Class {…}
Types for Messages • We use MSG as a type constructor for constructing types for messages • Given a type t, the type (t)MSGis for messages that require objects to return values of type t after receiving them.
Message Constructors • We can use some syntax to declare message constructors:MSGgetfst: (int) MSGMSGsetfst: int -> (unit) MSGMSGgetsnd: (int) MSGMSGsetsnd: int -> (unit) MSG
A Type for Objects • We can now use the following type for objects:OBJ = {’a}. ’a MSG -> ’a(We write {’a} for a)
Object Constructor for IntPairClass fun newIntPair (x:int, y:int): OBJ = letval xref = ref x val yref = ref yfun dispatch (MSGgetfst) = !xref | dispatch (MSGsetfst x’) = (xref := x’) | dispatch (MSGgetsnd) = !yref | dispatch (MSGsetsnd y’) = (yref := y’) | dispatch msg = raise UnknownMessage in dispatch end
A Serious Problem • As in Smalltalk, we so far have no differentiation between objects at type-level • For instance, let anIntPair be an object constructed by calling newIntPair. If we send a message MSGfoo to anIntPair, then an exception is to be thrown at run-time. • We would really like to have a type system that can stop this at compile-time
Class Tags • We treat classes as tags. • A message type is now of the form: (t)MSG(C), where C is a class tag. • For instance, MSGgetfst: (int)MSG(IntPairClass)MSGsetfst: int -> (unit)MSG(IntPairClass) • The type of an object is of the form:OBJ(C) = {’a}. ’a MSG(C) -> ’a • For instance,newIntPair: int * int ->OBJ(IntPairClass)
Parameterized Class Tags • There is an immediate need for class tags parameterized over types:MSGgetfst: {’a,’b}. (’a) MSG((’a,’b)PairClass) MSGsetfst: {’a,’b}. ’a -> (unit) MSG((’a,’b)PairClass) MSGgetsnd: {’a,’b}. (’b) MSG((’a,’b)PairClass)MSGsetsnd: {’a,’b}. ’b -> (unit) MSG((’a,’b)PairClass)
Object Constructor for PairClass fun newPair (x, y) = letval xref = ref xval yref = ref yfun dispatch (MSGgetfst) = !xref | dispatch (MSGsetfst x’) = (xref := x’) | dispatch (MSGgetsnd) = !yref | dispatch (MSGsetsnd y’) = (yref := y’) in dispatch end withtype {’a,’b}. ’a * ’b -> OBJ((’a,’b)PairClass)
Subclasses • We define a relation <= on class tags{’a,’b}. (’a,’b)ColoredPairClass <= (’a,’b)PairClass • The type of a message constructor is now of the following form:{’a1,…,’an,c <= C} t1 -> (t2) MSG(c) • For instance,MSGgetfst: {’a,’b,c<= (’a,’b)PairClass}. (’a) MSG(c)MSGsetfst: {’a,’b,c<= (’a,’b)PairClass}. ’a -> (unit) MSG(c)
Contravariant Use of SelfType class EqClass {MSGeq: SelfType -> bool;MSGneq: SelfType -> bool; } MSGeq: {c <= EqClass}. OBJ(c) -> (bool) MSG(c) MSGneq: {c <= EqClass}. OBJ(c) -> (bool) MSG(c)
Covariant Use of SelfType class objClass {… …MSGclone: SelfType… … } MSGclone: {c <= ObjClass}. (OBJ(c)) MSG(c)
Inheritance • We explain how inheritance can be implemented. class Int1Class inherits ObjClass { MSGget1: int; MSGset1: int -> unit; MSGdouble: unit => self(MSGset1(2 *self(MSGget1)));} The declaration essentially does the following:Int1Class <= ObjClassMSGget1: {c <= Int1Class} (int) MSG(c)MSGset1: {c <= Int1Class} int -> (unit) MSG(c)MSGdouble: {c <= Int1Class} (unit) MSG(c)
Super Functions • Each class is associated with a “super” function • Given a class C, the super function associated with C has the type:{c <= C}. OBJ(c) -> OBJ(c)
Examples of Super Functions • fun superObj (self) = letfun dispatch (MSGclone) = self | dispatch msg = raise UnknownMessagein dispatch endwithtype {c <= ObjClass} OBJ(c) -> OBJ(c) • fun superInt1 (self) = let fun dispatch (MSGdouble) =self(MSGset1(2 * self(MSGget1))) | dispatch msg = superObj self msgin dispatch endwithtype {c <= Int1Class} OBJ(c) -> OBJ(c)
Object Constructor for Int1Class • We implement an object constructor for the Int1Class as follows: fun newInt1 (x)= let val xref = ref x fun dispatch (MSGget1) = !xref | dispatch (MSGset1 x’) = (xref := x’) | dispatch (MSGclone) = newInt1 (!xref) | dispatch msg = superInt1 dispatch msgin dispatch endwithtype int -> OBJ(Int1Class)
A Class Declaration class Int2Class inherits Int1Class { MSGget2: int;MSGset2: int -> unit; } The declaration essentially does the following: Int2Class <= Int1Class MSGget2: {c <= Int2Class} (int) MSG(c) MSGset2: {c <= Int2Class} int -> (unit) MSG(c)
Super Function and Constructor for Int2Class fun superInt2 (self) = letfun dispatch msg = superInt1 self msg withtype {c <= Int2Class} OBJ(c) -> OBJ(c) fun newInt2 (x1, x2) = letval x1ref = ref x1val x2ref = ref x2fun dispatch (MSGget1) = !x1ref | dispatch (MSGset1 x) = (x1ref := x) | dispatch (MSGget2) = !x2ref | dispatch (MSGset2 x) = (x2ref := x) | dispatch msg = superInt2 dispatch msg in dispatch end withtype int * int -> OBJ(Int2Class)
Some Interesting Questions • Let O2 be an object created by calling newInt2 • What happens if we send the message MSGdouble to O2: O2(MSGdouble) ? • No method is implemented for MSGdouble in newInt2 • No method is implemented for MSGdouble in superInt2 • A method lookup finds it through superInt1 • What happens if we sent the message MSGclone to O2: O2(MSGclone) ? • No method is implemented for MSGclone in newInt2 • No method is implemented for MSGclone in superInt2 • No method is implemented for MSGclone in superInt1 • A method finds it through superObj
Subtyping • Given a class tag C,OBJECT(C) = [c <= C] OBJ(c)(we use […] for the existential quantifer ) • E.g.,OBJ((OBJECT(eqClass),OBJECT(eqClass))pairClass)is the type for pairs whose both components support equality test
Type Representation typecon (type) TY = (int) TYint| {’a,’b}. (’a * ’b) TYtup of ’a TY * ’b TY| {’a,’b}. (’a -> ’b) TYfun of ’a TY * ’b TY| {’a}. (’a TY) TYtyp of ’a TY TYint: (int) TY TYtup: {’a,’b}. ’a TY * ’b TY -> (’a * ’b) TY TYfun: {’a,’b}. ’a TY * ’b TY -> (’a -> ’b) TY TYtyp: {’a}. ’a TY -> (’a TY) TY
An Example: val2string fun val2string pf x =case pf of TYint => int2string x| TYtup (pf1, pf2) => “(” ^ val2string pf1 (fst x) ^ “,” ^ val2string pf2 (snd x) ^ “)”| TYfun _ => “[a function]”| TYtyp _ => “[a type]” withtype {’a}. ’a TY -> ’a -> string
End of the Talk • Thank You! Questions?
H.O.A.S. Trees typecon (type) HOAS = {’a}. (’a) HOASlift of ’a| {’a}. (’a) HOASif of bool HOAS * ’a HOAS * ’a HOAS| {’a,’b}. (’a * ’b) HOAStup of ’a HOAS * ’b HOAS| {’a,’b}. (’a -> ’b) HOASlam of ’a HOAS -> ’b HOAS| {’a}. (’a) HOASfix of ’a HOAS -> ’a HOAS HOASlift: {’a}. ’a -> ’a HOAS HOASif: {’a}. bool HOAS * ’a HOAS * ’a HOAS -> ’a HOAS HOAStup: {’a,’b}. ’a HOAS * ’b HOAS -> (’a * ’b) HOAS HOASlam: {’a,’b}. (’a HOAS -> ’b HOAS) -> (’a -> ’b) HOAS HOASfix: {’a}. (’a HOAS -> ’a HOAS) -> ’a HOAS
Type-Preserving Evaluation fun eval (HOASlift v) = v| eval (HOASif (b, e1, e2)) = if eval (b) then eval (e1) else eval (e2)| eval (HOAStup (e1, e2)) = (eval (e1), eval (e2))| eval (HOASlam (f)) = fn x => eval (f (HOASlift x))| eval (HOASfix f) = eval (f (HOASfix f)) withtype {’a}. ’a HOAS -> ’a
F.O.A.S. trees typecon (type,type) FOAS =| {’a,’g}. (’a,’g) FOASlift of ’a| {’a,’g}. (’a,’a*’g) FOASone| {’a,’b,’g}. (’a,’b*’g) FOASshift of (’a,’g) FOAS| {’a,’b,’g}. (’a -> ’b,’g) FOASlam of (’b,’a*’g) FOAS| … FOASlift: {’a,’g}. ’a -> (’a,’g) FOAS FOASone: {’a,’g}. (’a,’a*’g) FOAS FOASshift: {’a, ’b,’g}. (’a,’g) FOAS -> (’a,’b*’g) FOAS FOASlam: {’a,’b,’g}. (’b,’a * ’g) FOAS -> (’a -> ’b,’g) FOAS …
Type-Preserving Evaluation fun eval (FOASlift v) env = v| eval FOASone (v, env) = v| eval (FOASshift e) (_, env) = eval e env| eval (FOASlam e) env = fn x => eval e (x, env)| … withtype {’a}. (’a,’g) FOAS -> ’g -> ’a fun evaluate (e) = eval e () withtype {’a}. (’a,unit) FOAS -> ’a