Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                

Strong typing of object-oriented languages revisited

ACM SIGPLAN Notices, 1990
...Read more
Strong Typing of Object-Oriented Languages Revisited Ole Lehrmann Madsen Boris Magnusson Computer Science Department, Aarhus University Department of Computer Science, University of Lund Ny Munkegade, DK-8000 Aarhus C, Denmark PoBox 118, S-221 00 Lund, Sweden Tlf.: +45 6 12 71 88 - E-mail: olmadsenOdaimi.dk TX: +46 46 10 80 44 - E-mail: bori&dna.lth.se Birger MoBer-Pedersen Norwegian Computing Center P.O. Box 114, Bliudern, N-0314 Oslo 3, Norway TK: +47 2 45 35 00 - E-mail: birgerOnr.no Abstract This paper is concerned with the relation between sub- typing and subclassing and their influence on program- ming language design. Traditionally subclassing as in- troduced by Simula has also been used for defining a hierarchical type system. The type system of a lan- guage can be characterized as strong or weak and the type checking mechanism as static or dynamic. Param- eterized classes in combination with a hierarchical type- system is an example of a language construct that is known to create complicated type checking situations. In this paper these situations are analyzed and several different solutions are found. It is argued that an ap- proach with a combination of static and dynamic type checking gives a reasonable balance also here. It is also concluded that this approach makes it possible to base the type system on the class/subclass mechanism. 1 Introduction The purpose of this paper is to contribute to the clar- ification of typing issues in object-oriented languages. The issue of type checking in languages that has classes with type parameters have recently been the subject of several papers [2, 10, 111. There has also been proposals for introducing a separate type system supplementary to the subclass hierarchy [l]. We will investigate these problems from the point of view taken in the Scandinavian school of object-oriented Permission to copy without fee all or part of this material is granted provided that the copies are not made or distributed for direct commercial advantage, the ACM copyright notice and the title of the publication and its date appear, and notice is given that copying is by permission of the Association for Computing Machinery. To copy otherwise, or to republish, requires a fee and/or specific permission. Q 1990 ACM 089791-411-Z/90/0010-0140...$1.50 programming [4, 8, 121. The class concept was intro- duced in Simula [3] and its motivation was to model concepts in the application domain. This lead to the introduction of subclassing mechanisms as a means to represent specialization and generalization hierarchies. Inheritance of properties in these hierarchies was the main motivation to introduce subclassing. Viewed as a modeling mechanism, subclassing was also taken to define a hierarchical type system. Types are defined ex- plicitly which means that separate classes with the same internal structure define different types. This approach has been followed in Smalltalk [5], Beta [7], C++ [15] and Eiffel [lo]. Apart from being used for (1) modeling as originally intended, subclassing has also been used as a means of specifying (2) inheritance of code or “code sharing”. A type system can also be viewed in two different ways (1) as a means for representing concepts in the application domain and (2) as a means for detecting certain kinds of program errors, type errors. The first aspect, rep- resentation of concepts, is covered by both mechanisms and subclassing has been used also to define a hierarchi- cal type system which have thus made a separate type system unnecessary. The strength of the type system is also a related is- sue. We regard this as a continuum where weakly typed means that the type of an expression carries little or no information. Smalltalk is an example of such a lan- guage where the type of instance variables convey very little information on what messages are legal to send to the denoted object. A perfectly strongly typed language would exclusively have expressions where its type car- ries all information about the denoted object. We are not aware of any such object-oriented language although some other languages come close [16]. Languages with a hierarchical type system and qualified references serve as a compromise since some, but not necessarily all, operations on an object can be inferred from the qual- ification of the reference. Strong typing also implies that type checking can be performed during compila- 140 ECOOP/OOPSLA ‘90 Proceedings October 21-25, 1990
tion, but utilization of weak typing aspects has to be checked during run-time. Weak typing and run-time type checking provides a greater flexibiity for the programmer. This flexibility is especially appreciated when developing the initial pro- totypes of a system. Compile-time type checking is use- ful for three purposes: (1) it improves the readability of programs (2) it makes it possible to detect a cer- tain class of program errors at compile-time and (3) it makes it possib1.e to generate more efficient code. A pro- gramming language must offer a proper balance between flexibility and compile-time checking to be useful. Most so-called strongly typed languages rely on a combination of compile-time type checking and run- time type checking. Simula and BETA are examples of such languages. Here a large class of type errors are caught at comIlile-time, whereas others are left to run- time checks. C-t+ is an example utilizing compile-time type checking i,o some extent, but situations that can not be caught during compilation are not cared for dur- ing run-time. Biffel is an example of a language which has been designed to permit a high degree of compile- time type checking, but there are, however, also situa- tions where run-time type checking has to be used. Parameterized classes in combination with a hierar- chical type- system are known to create complicated type checking situations [2, 10, 111. Similar situations can be created with a variety of language constructs in most object-oriented languages. This paper is using the type system of BETA, which extends the type system of Simula, for illustrating the problems. The extension allows for classes parameterized with classes (types) by means of so-called virtual classes as described in [S]. The type checking problems that arise in such situations are analyzed and alternative solutions are described. It is argued that an approach with a combination of static and dynamic type checking gives a reasonable bal- ance also here, again avoiding a separate type system. Language consi,ructs which can be used to reduce the amount of runGme checks in certain situations are also discussed. In addition sl3rne of the examples from [l] are shown in BETA. The notions of qualified references, remote access and refelmence assignment as described in section 2-4 are the same in BETA and Simula. The language notation used in this paper is a modified version of BET.A similar to the notation used in [9]. 2 QualiIied references Consider the foilowing class hierarchy: Vehicle: c.lass (# owner: @integer; licenselo: @integer #> ; Bus: class Vehicle (# no0fSeats: Qinteger;#); Truck : class Vehicle (# tonnage: @integer #>; Car: class Vehicle (# #I; aVehicle: n Vehicle; aBus : - Bus; aTruck: - Truck; The classes Bus, Truck, and Car are subclasses of class Vehicle. The attributes owner, licenselo, noOfSeats, and tonnage are local integer variables, while avehicle, aBus, and aTruck are references (or pointers) to objects. A reference is qualified by a class. The qualification restricts the set of objects that the reference may refer to. The reference aVehicle is qualified by Vehicle. This implies that aVehicle may denote instances of class Vehicle and instances of subclasses of Vehicle. The reference aBus may denote instances of class Bus and instances of possible subclasses of Bus. It is, however, not possible for the reference aBus to denote instances of class Vehicle, Truck, and Car. In order to describe the type system more precisely we will introduce some formal, but yet very simple no- tation. The class hierarchy in Beta (and other object-oriented languages) can be described as a lattice since the classes are partially ordered by the relation subclass of or the symbol c (the relation > is used for superclass of). A pre-defined class with the property of being the super- class of all other classes are sometimes explicitly avail- able in the language (class Object in Smalltalk and Beta). In the lattice this class plays the role of Top. Object Vehicle Buia I I NoClass The special class NoClass is a subclass of all other classes. It plays the role of bottom in the Lattice. A ref- erence that refers to no object has the value NONE. The October 21-25, lQ!JO ECOOPIOOPSLA ‘90 Proceedinp 141
Strong Typing of Object-Oriented Ole Lehrmann Computer Science Department, Languages Boris Madsen Aarhus Magnusson of Computer Science, University of Lund PoBox 118, S-221 00 Lund, Sweden TX: +46 46 10 80 44 - E-mail: bori&dna.lth.se Department University Ny Munkegade, DK-8000 Aarhus C, Denmark +45 6 12 71 88 - E-mail: olmadsenOdaimi.dk Tlf.: Birger Revisited MoBer-Pedersen Norwegian Computing Center P.O. Box 114, Bliudern, N-0314 Oslo 3, Norway TK: +47 2 45 35 00 - E-mail: birgerOnr.no Abstract This paper is concerned with the relation between subtyping and subclassing and their influence on programsubclassing as inming language design. Traditionally troduced by Simula has also been used for defining a hierarchical type system. The type system of a language can be characterized as strong or weak and the type checking mechanism as static or dynamic. Parameterized classes in combination with a hierarchical typesystem is an example of a language construct that is known to create complicated type checking situations. In this paper these situations are analyzed and several different solutions are found. It is argued that an approach with a combination of static and dynamic type checking gives a reasonable balance also here. It is also concluded that this approach makes it possible to base the type system on the class/subclass mechanism. 1 Introduction The purpose of this paper is to contribute to the clarification of typing issues in object-oriented languages. The issue of type checking in languages that has classes with type parameters have recently been the subject of several papers [2, 10, 111. There has also been proposals for introducing a separate type system supplementary to the subclass hierarchy [l]. We will investigate these problems from the point of view taken in the Scandinavian school of object-oriented Permission to copy without fee all or part of this material is granted provided that the copies are not made or distributed for direct commercial advantage, the ACM copyright notice and the title of the publication and its date appear, and notice is given that copying is by permission of the Association for Computing Machinery. To copy otherwise, or to republish, requires a fee and/or specific permission. Q 1990 ACM 089791-411-Z/90/0010-0140...$1.50 140 programming [4, 8, 121. The class concept was introduced in Simula [3] and its motivation was to model concepts in the application domain. This lead to the introduction of subclassing mechanisms as a means to represent specialization and generalization hierarchies. Inheritance of properties in these hierarchies was the main motivation to introduce subclassing. Viewed as a modeling mechanism, subclassing was also taken to define a hierarchical type system. Types are defined explicitly which means that separate classes with the same internal structure define different types. This approach has been followed in Smalltalk [5], Beta [7], C++ [15] and Eiffel [lo]. Apart from being used for (1) modeling as originally intended, subclassing has also been used as a means of specifying (2) inheritance of code or “code sharing”. A type system can also be viewed in two different ways (1) as a means for representing concepts in the application domain and (2) as a means for detecting certain kinds of program errors, type errors. The first aspect, representation of concepts, is covered by both mechanisms and subclassing has been used also to define a hierarchical type system which have thus made a separate type system unnecessary. The strength of the type system is also a related issue. We regard this as a continuum where weakly typed means that the type of an expression carries little or no information. Smalltalk is an example of such a language where the type of instance variables convey very little information on what messages are legal to send to the denoted object. A perfectly strongly typed language would exclusively have expressions where its type carries all information about the denoted object. We are not aware of any such object-oriented language although some other languages come close [16]. Languages with a hierarchical type system and qualified references serve as a compromise since some, but not necessarily all, operations on an object can be inferred from the qualification of the reference. Strong typing also implies that type checking can be performed during compila- ECOOP/OOPSLA‘90 Proceedings October 21-25, 1990 tion, but utilization of weak typing aspects has to be checked during run-time. Weak typing and run-time type checking provides a greater flexibiity for the programmer. This flexibility is especially appreciated when developing the initial prototypes of a system. Compile-time type checking is useful for three purposes: (1) it improves the readability of programs (2) it makes it possible to detect a certain class of program errors at compile-time and (3) it makes it possib1.eto generate more efficient code. A programming language must offer a proper balance between flexibility and compile-time checking to be useful. Most so-called strongly typed languages rely on a combination of compile-time type checking and runtime type checking. Simula and BETA are examples of such languages. Here a large class of type errors are caught at comIlile-time, whereas others are left to runtime checks. C-t+ is an example utilizing compile-time type checking i,o some extent, but situations that can not be caught during compilation are not cared for during run-time. Biffel is an example of a language which has been designed to permit a high degree of compiletime type checking, but there are, however, also situations where run-time type checking has to be used. Parameterized classes in combination with a hierarchical type- system are known to create complicated type checking situations [2, 10, 111. Similar situations can be created with a variety of language constructs in most object-oriented languages. This paper is using the type system of BETA, which extends the type system of Simula, for illustrating the problems. The extension allows for classes parameterized with classes (types) by means of so-called virtual classes as described in [S]. The type checking problems that arise in such situations are analyzed and alternative solutions are described. It is argued that an approach with a combination of static and dynamic type checking gives a reasonable balance also here, again avoiding a separate type system. Language consi,ructs which can be used to reduce the amount of runGme checks in certain situations are also discussed. In addition sl3rne of the examples from [l] are shown in BETA. The notions of qualified references, remote access and refelmenceassignment as described in section 2-4 are the same in BETA and Simula. The language notation used in this paper is a modified version of BET.A similar to the notation used in [9]. 2 QualiIied (# no0fSeats: Truck : class Vehicle Car: class (# tonnage: Vehicle Qinteger;#); @integer #>; (# #I; aVehicle: n Vehicle; aBus : - Bus; aTruck: - Truck; The classes Bus, Truck, and Car are subclasses of class Vehicle. The attributes owner, licenselo, noOfSeats, and tonnage are local integer variables, while avehicle, aBus, and aTruck are references (or pointers) to objects. A reference is qualified by a class. The qualification restricts the set of objects that the reference may refer to. The reference aVehicle is qualified by Vehicle. This implies that aVehicle may denote instances of class Vehicle and instances of subclasses of Vehicle. The reference aBus may denote instances of class Bus and instances of possible subclasses of Bus. It is, however, not possible for the reference aBus to denote instances of class Vehicle, Truck, and Car. In order to describe the type system more precisely we will introduce some formal, but yet very simple notation. The class hierarchy in Beta (and other object-oriented languages) can be described as a lattice since the classes are partially ordered by the relation subclass of or the symbol c (the relation > is used for superclass of). A pre-defined class with the property of being the superclass of all other classes are sometimes explicitly available in the language (class Object in Smalltalk and Beta). In the lattice this class plays the role of Top. Object Vehicle Buia I I NoClass c.lass (# owner: @integer; licenselo: @integer October 21-25, lQ!JO Bus: class Vehicle references Consider the foilowing class hierarchy: Vehicle: #> ; The special class NoClass is a subclass of all other classes. It plays the role of bottom in the Lattice. A reference that refers to no object has the value NONE.The ECOOPIOOPSLA ‘90 Proceedinp 141 class of NONE is NoClass. Class NoClass is introduced for purely technical reasons. We also introduce two functions with the following definitions: object0 returns the object that a reference actually denotes, qua10 returns the formal class of a reference or the actual class of an object. The declaration R: - T implies that quaI = T. This means that qual(R), where R is a reference, may be computed at compiletime. If X is an object, then qual(X) is also constant. Consider aVehicle.noOfSeats; aBus . tonnage ; In these simple examples the attributes have all been instance variables, but procedures may also be attributes with the same rules for legal attribute access. Remote access of procedure attributes correspond to message sending in Smalltalk. In Smalltalk reference variables are not qualified, so it is not possible to check at compile-time that a message is legal. Assuming that noOfSeats is a method attribute, a Smalltalk expression like aVehicle new TCI -> RCI The action new T generates an object X such that qual(X) = T. The purpose of NoClass is to make sure that qual(ob ject (R)) is well defined for any reference R. For the value NONEwe have qual(NONE) = NoClass C T for any class T which is not NoClass. The function object(R) varies at run-time, since R may denote different objects. The role of the function object () is to express dependency on run-time behavior. The idea of qualified references can now be stated with the following relation that always must be true for all references in a program: qual(object(ref)) c qual(ref) As an example consider the reference aVehicle.The lation qual(object(aVehicle)) re- 3 Remote access of attributes The qualification of references is used to check at compile-time that remote-access of attributes is legal. The following remote-identifiers are legal: 4 Reference assignment Qualified references provide less flexibility than unqualified references which may denote objects of any class. The cost of this flexibility is a run-time check for each message sending. The comparable cost for qualified references is significantly smaller: A nln-time check that will take place at some cases of reference assignment. Consider the following example: new Bus [] -> aBus [] ; aBus [] -> aVehicle[] aVehicleC1 -> aBus Cl aTruckC1 -> aBus Cl Cl3 C23 c33 c43 In {l} a new instance of Bus is generated. The instance reference is assigned to the reference aBus. This statement is trivially legal and this can be checked during compilation. From qual(object(new Bus)) = Bus and qual(aBus) aVehicle.ouner; aBus. owner; aBus .noOfSeats; aTruck. owner; aTruck. tonnage ; = Bus it follows that qual(object(newBus)) As an example the first remote access is legal since qual(aVehicle) = Vehicle and the class Vehicle specifies an attribute owner and so on. The following remote-identifiers are illegal: 142 will give rise to the run-time error: “Message not understood”. The qualification of references makes it possible to check at compile-time that this kind of error does not occur. There is still the possibility that a reference may be NONE, which gives rise to an error, but this is not considered a type-checking problem. & qual(aVehicle) expresses the same restriction on what objects the reference aVehicle may denote as expressed above in English. noOf Seats C qual(aBus) This ensures that qual(object(aBus)) E qual(aBus) holds true after the assignment. ECOOP/OOPSLA ‘90 Proceedings October 21-25, 1990 The assignment in (2) is legal since aVehicle may denote Bus objects. The legality may be (and is) determined at com$e-time, i.e. no run-time checking will take place. The legality can be statically checked since qual(object(aBus)) E Bus and Bus C qual(aVehicle) = Vehicle The assignmert in {3) is legal if aVehicle denotes an instance of Bus. In general this may not be detected at compile time. This implies that a run-time type check will be performed in this case. This can be seen since the static information quall:object(aVehicle)) C Vehicle cannot be used to guarantee that qual(ob ject (aBus)) 5 Bus is true after the assignment. The compiler emit a run-time check to ensure that qual(object(aVehicle)) must thus C Bus The assignment in (4) is illegal, since it is not possible for aTruck to denote a Bus object. Thii is statically checkable since, qual(object(aTruck)) E qual(aBus) give rise to comparison between Truck and Bus and these two classes are not ordered. In general a:n assignment statement AC1->BCl (B:-A or B : =A in otht:r languages) must be checked to fulfill qual(object(A)) 2 qual(B) in order to ensure that qual(object(B)) E qual(B) holds after the assignment. If qual(A) E qual(B) this is statically correct, but if qual(A) > qurrl(B) then the compiler must ensure that qual(ob ject (A)) & qual(B) at run-time. In most cases assignments are as in (2) (or qualifications are equal) and no run-time checking is needed. The ability to weaken the type information on an object as in 13) is very usable in order to write general code like queue and list manipulation etc. The problem of managing a queue of Vehicles is described already in [4]. The result of a queue operation, like returning the first object, is such a weakly qualified reference. The possibility to explicitly strengthen the type information again (as in {:3}) is vital. This gives the programmer October 21-25, 1990 the possibility to view an object at different levels of abstraction. In the example above one would typically use the following views: as an element in a queue, as a Vehicle, and as a Bus or Truck. Consider the example of three registers: A Vehicle register, a Bus Register, and a Truck register. The Vehicle register may contain references to both Bus objects and to Truck objects. The qualification of references used to build up the Vehicle register will therefore be Vehicle. The parameter to an insert operation on the Vehicle register will be qualified by Vehicle, and operations for getting references to objects in the register will deliver references qualified by Vehicle. Similarly the Bus register will use references qualified by Bus. The task of extracting Bus objects from the general Vehicle register and entering them into the Bus register will necessarily involve an assignment like that of {a}. In Simula, BETA and C++ this kind of assignment is possible. Release 2.2 of Eiffel offers a new assignment operator. The assignment in (3) will be anus ?= aVehicle in Eiffel. This operator assigns the object to z&us if legal according to the rules given above, NONE otherwise. The execution of this operator thus gives rise to an implicit run-rime check. 5 Value assignment In this section the notion of value assignment is handled. Rules similar to those for reference assignment apply, and the same kind of run-time type checking apply. By value assignment is meant some form of copying of state of the objects. The exact way of copying differs from language to language. For the purpose of this paper we will assume that value assignment means copying the state of the source object to the state of the destination object. We introduce two functions: copy(ri,r2) copies the contents of ri to r2 assuming that qual(rl)=qual(r2), while project(r,qual) selects the qual-part of r. The variables ri, r2 and r all denote objects. Value assignment in BETA has the form: aBus -> aBus The assignment above will have the effect that the values of the attributes owner, licenseNo and noOf Seats of a~usi are copied to the corresponding attributes of aBus2, that is copy(aBusl, aBus2) We are here assuming that aBus fied by Bus, that is ECOOPlOOPSLA ‘90 Proceedings qual(aBus1) and aBus2 are quali- = qual(aBus2) = Bus 143 Consider the following value assignment: aBus -> aVehicle (13 The reference anus refers to an object that is at least a Bus object and avehicle refers to an object that is at least a Vehicle. Only the Vehicle attributes of the object denoted by aBus may be copied to the object denoted by avehicle. This is stated by the expression: COPY(~~, r2) where xi = project(object(aBua), Vehicle) and r2 = project(object(aVehicle), Vehicle) The following picture illustrates the situation. Only the “Vehicle bits” of the object referred to by aBus are copied to the corresponding “Vehicle bits” of the object referred to by avehicle. The dots . . . indicates that these objects may in fact be “larger” than can be deferred from the formal qualification of their references. I aBus aVehicle Consider next an assignment of the form: aVehicle -> aBus <2) The situation here is opposite to the previous situation. A potential “smaller” object is copied to a potential “larger” object. At compile-time it is known that the two objects at least have the Vehicle attributes in common. Another possibility would be to require that avehicle denotes an object qualified by Bus. (This would be analogous to the situation with reference assignments of the form aVehicle [I -> aBus [I .) In this case the Bus attributes could be copied. With the above semantics of value assignment it is determinable at compile-time which bits to copy from the source object to the destination object. This is essentially the semantics of value assignment in BETA.’ This semantics has the disadvantage that in some situations some information (bits) may be lost. Consider case (1): If aVehicle actually refers to a Bus object, lSee, however, assignment 144 [7] for s more precise description of value then all the attributes of the Bus object referred to by anus could be copied to the Bus object referred to by avehicle. In general one could find the largest common subclass of the actual objects being denoted and then copy the common attributes. In most languages value assignment is defined as a pure copying of bits. In this way the state of one object may be forced upon another object. Often different object states may denote the same abstract value. It is therefore not always desirable to define the semantics of value assignment as a bitwise copying. The situation is even worse when considering equality. Here a bitwise comparison of two objects may not correspond to equality of the abstract values represented by the two objects. In general it is difficult to suggest language constructs for handling value assignment and equality. Finally we notice that in most work on hierarchical type-systems the distinction between reference semantics and value semantics of assignment and equality does not seem to be explicit. In relation to object-oriented programming this distinction is, however, crucial. 6 Classes ters with 9ypey9 parame- Virtual classes in BETA and generic classes in Eiffel makes it possible to define classes parameterized with other classes or types. These are very powerful language mechanisms, but they also complicate the rules for checking the legality of assignments. The following examples show that subclassing and the rules for assignment associated with subclasses may well be used for expressing types of parameters. We will use it as basis for the analysis. The discussion will be generalized at the end of the section. Consider a procedure for entering any vehicle into some kind of register. As part of entering, the vehicle is going to get a license number (the owner of the vehicle is supposed to be given). newLicenseNo is a function that delivers a new integer value: Insert: proc (# par: * Vehicle enter par Cl do neuLicenseNo -> par. licenseNo Center par into register3 a) ; As the parameter par is known to denote instances of at least class Vehicle, it is safe to access the licenseNo attribute. If aBus denotes an instance of class Bus, that is a subclass of Vehicle, which is the “type” of the parameter, then the following invocation of Insert should be legal: ECOOPIOOPSLA ‘90 Proceedings October 21-25. 1990 aBusC1 -> Insert Type legality of this is handled by the rule for reference assignment (from anus Cl to the parC1 parameter of Insert). Consider the Insert to be part of a general Register class, paramelierized by the type of vehicles to be in the register. The intention is to define specialized Registers restricted to hold objects of class Bus (and its subclasses), and similar specialized registers for Truck and Car objects. The qualification, Type, of the parameter, par, to the procedure Insert is declared virtual. This is done in order to strengthen its qualillcation in specializations of Register. A general Register is able to hold any vehicle, i.e. references to o’bjects of the classes Bus, Truck, Car and also Vehicle objects. qual(per)=Type c Vehicle Since Type may have different extensions in different subclasses of Register, we cannot determine qual(par) at compile-time. The qualification aRegister.Type depends on the qualification of aRegister. We have the following asser tions: = Vehicle aRegister.Type if qual(object(aRegister)) Register: class (# Typ3 : virtual class Vehicle; Ins’srt : virtual proc (# par: *Type lanter par Cl do . . . . neuLicenseNo->pax.licenseNo; .a.; INNER; ..a :tc>; #I i = Register and aRegister.Type= Bus if qual(object(aRegister)) = BusRegister In general each subclass of Register gives rise to an assertion of this kind. We use the notation object(aRegister).Type Given this general class the following subclass of Register is a class of registers that may only hold buses. This is accomplished by restricting the virtual class Type to Bus: to denote this virtual qualification. The qualification of the parameter par also depends on the qualification of aRegister. We use the notation qual(object(aRegister).Insert.par) BusRegist er : class Register (# Type: extended class Bus; Insert: extended proc (U do (par.noOfSeats,par.owner[]) ->checkValidity; INNER #I #I ; to denote the qualification We can deduce that of a specific par. object(aRegister).Type C Vehicle and qual(object(aRegister).Insert.par) Restricting the: type Type to Bus serves two purposes. It enables us to say that the parameter to the procedure Insert must b,t! at least a Bus which will then guarantee that non-Buses will not be inserted (there is most likely also some internal data structures in the Register, not shown in the oxample, where the same restriction apply). The problem of how to enforce this restriction is the topic of the: rest of this section. The second purpose is that inside the BusRegister it will be safe to access attributes of class Bus. The expression Pax .noOf Seats is an example of that. This add to the expressiveness of the language and can be statically type-checked. October 21-25, 1990 The qualification of a virtually qualified reference is in general not known at compile time since the qualification can be extended to a more specific class. In our example par is qualified Vehicle in a Register, but as Bus in a BusRegister. For a reference like par qualified by a virtual class we have the following relation: E Vehicle but these relations are of little practical use when determining the legality of assignments to par as will be seen below. For an assignment aVehicle[] -> aRegister.Insert[] we must prove that after the assignment holds: the following qual(object(aRegister.Insert.par)) C_qual(object(aRegister).Insert.par) ECOOP/OOPSLA‘90 Proceedings 145 This relation states that the qualification of the object referred to by par must be a subclass of the qualification of par associated with the object actually referred to by aRegister. Since the left-hand side of this relation is c Vehicle we can conclude that the demand on the object referred to by avehicle is at least a Vehicle, but it may be stronger. In the following we analyze three situations of assignment to virtually qualified references. In case 1 we consider an assignment independent of its context. In cases 2 and 3 we take the context of the assignment into consideration. In all 3 cases we assume the following declarations: avehicle: aRegister: qual(object(aRegister)) Cl3 -> aRegister.Insert; The problem here is that object(aRegister) cannot compile-time. If the qualification be determined qual(object(aRegister)) qual(aRegister.Insert.Par) = aRegister.Type = Vehicle qual(aVehicle) and the reference Bus C Vehicle. = relation will Bus be fulfilled since of at qual(object(aRegister)) = BusRegister Bus = Vehicle qual(aVehicle) (1) is legal. so the reference relation If on the other hand qual(object(aRegister)) = BusRegister then aRegister.Type and the assignment {I) (33 qual(BusRegister.Insert.par) = BusRegister.Type = Register = aRegister.Type ; Here we have: then and the assignment Register new BusRegister [I -> aRegiater[l new VehicleCl -> aVehicle Cl ; aVehicle[] -> aRegister.Insert; Case 1: Consider the assignment = Bus is legal only if qual(object(aVehicle)) s Bus This implies that in the general case assignments to virtually qualified references can not be statically checked. Given more information from the program it is in principle possible to calculate an upper bound on the needed qualification. This is done in the next two cases. Case 2: Consider the imperatives: new Register Cl -> aRegister [] ; -> aVehicle 11; new Bus[] aVehicle [I -> aRegister. Insert ; 146 = Case 3: Consider the imperatives: -Vehicle; Register; aVehicle Here we have: C23 = Vehicle is violated in this case. The examples l-3 shows that the assignment can not be statically type checked in the general case. If the compiler can infer the type of an object using dataflow analysis it can statically check both 2 and 3. Full dataflow analysis is not possible, but a limited form, only targeted to recognize the case when a reference can be guaranteed to have exactly its declared qualification has been proposed for Eiffel [ll]. This effect can also be achieved in Beta with part objects that are statically allocated. (See below). In [13] the same effect is achieved for so-called homogeneous variables which are type exact. The type checking problem described above is general and occurs in a couple of different language constructions. The use of virtual qualification was chosen to illustrate the problem above. Classes with type parameters shows the same problem when subclasses are allowed to strengthen the qualification on the type parameter. The same effect can also be achieved with selfrelative types as “like Current” and “thisclass”. Yet another example is classes with virtual procedures. If ECOOP/OOPSLA‘90 Proceedings October 21-25, 1990 subclasses are allowed to restrict the type of the parameters to re-implementations of procedures the same problem occurs again. The heart of the problem can be explained by observing that the notion of qualified references does not help us in this calse. The essence of qualified references is to guarantee that a reference is denoting at least an object of a certain class. This is useful because it is then safe to assume that the object has attributes of that class. When assigning an object to such a reference the compiler need to calculate the qualification of the reference as described in section 4, and the legality of the assignment can in many cases be checked statically. Qualified references allows us to determine a least qualification of an object, but the group of constructions described earlier in thii section introduces objects where the demands may increase with the qualification. The notion of qualified references can not help us to calculate an upper bound on these demands. We have found the following three different ways to safely handle type-checking of type-parameterized constructions: 1. Not allowing 2. Introducing the type-demands to be strengthen references that are type exact been to allow also constructions that require run-time type checking. This route has been followed here, but also for dynamic strengthening of qualification as described in section 4. Limiting run-time checks In general the use of virtual classes will involve runtime checking. BETA has constructs that makes it possible to avoid some of these run-time checks. In class BusRegister, the virtual class Type may be defined using fixed instead of extended. This implies that Type is not a virtual class in BusRegister, so it cannot be further extended in subclasses of BusRegister. The same effect can be obtained by declaring a static (part) object like: aFixedE%usRegister: @BusRegister Here it is also known that no further extension of Type is possible, so the qualification of par in Insert is fixed to the class Bus. The reason is that virtual classes and procedures may only be extended in subclasses, and aFixedBusRegister is not a class, but an object. Note that aFixedBusRegister is in fact a type exact reference. 3. Run time checks The first solution has been adopted for example in Simula for virtual procedures with specified parameters and for arrays of references used as parameters which must conform exactly. For these situations it works also in practice due to the possibility of dynamically strengthening the qualification of an object. This solution have also been proposed in [2] where it is suggested that it should not be allowed to strengthen the type demands in subclasses, but only weakening them. Weakening typeconstraints is also possible in Trellis/Owl [14], but seems to be of very limited practical value and will in practice mean that a fixed type will be used. In BETA a virtual class can be fixed in a subclass (see below) with the meaning that a declaration can not be further strengthen in a subclass. [2] suggests the technique with weakening the demands to be used also for classes with type parameters. Also here weakening is of questionable practical value. It should be noted that BETA offers this as an alternative, while [2] suggest this to be the on1.y alternative. The second solution is exemplified with part objects in Beta and the suggested type-enforce rule in Eiffel. One can alsa consider to introduce a new kind of references which always denote objects belonging to exactly the declared class. The choice: is between expressive power and statically type-checkable constructions. In Beta the choice has October 21-25, 1990 ‘7 Subclassing versus subtyping In [l] it is claimed the need for a special interface inheritance hierarchy that is different from the class/subclass hierarchy, and that the interface hierarchy should be used for type checking purposes (and not the class/subclass hierarchy). The following example is the BETA version of some of the examples from [l], and it demonstrates that it is possible to use the class/subclass hierarchy for type checking. As the previous examples in this paper it also introduces the need for run-time type checking. Even though the BETA approach is to use the class/subclass hierarchy for type checking, this is not the same as to say that we do not want to distinguish between interface and implementation of a class. The language has a separate mechanism for that [6], but this will not be covered here. The example demonstrates that it is possible to let a ColorPoint be a subclass of Point, and still have procedures local to Point, such as e.g. Equal, also work for objects of the subclass. Point: class (# X,Y: ainteger; ECOOP/OOPSLA‘90 Proceedings Move: virtual (# dx,dy: proc @integer; 147 P: ThisClass enter(dx,dy) do new ThisClass I3 -> PC3 ; x + dx -> P.x; y + dy -> P.y; INNER exit #I ; Equal: virtual proc (iv P: ThisClass; eq: @boolean P Cl do ((x=P.x) and (y=P.y)) -> eq; INNER exit eq #I #I ; In the same way as the language contains the special reference expression ThisObject giving the object in which the expression is evaluated, it also makes use of the pseudo-name ThisClass. The class ThisClass is the class of ThisObject, i.e. ThisClass = qual(object(ThisObject)) In order for the parameter P of Equal to work not only for Point, but also for subclasses of Point, it is quaIified by ThisClass. In a Point object the qualification ThisClass is Point. Consider the subclass ColorPoint of Point: ColorPoint: class (# c: @color; Point Move: extended proc (# do c -> P.c; INNER #>; Equal : extended proc (# do (eq and (c=P.c)) INNER #I class Pl,Pr: Cl,Cr: ColorPoint n Point; - ColorPoint Pl Cl ->Pr Cl Cl ->Cr ClCl ->Pr PlCl ->Cr . Equal; . Equal; .Equal; .Equal; 03 c23 (31 c41 In aII these four cases there has to be inserted run-time tests analogous to that of the example in section 6. Assuming that Pr and Pl actually denote Points while Cl and Cr denote ColorPoints only the last case wiII actually fail during run-time. This is because we try to compare a Point object and a ColorPoint object by executing the Equal procedure of the ColorPoint object with the Point object as the parameter P. As pointed out in [l] this would lead to evaluation of the expression P . c, with P denoting a Point object, and this is invalid because a Point object does not have an attribute c. The following is examples of situations where runtime checking may be avoided by using part objects. Suppose that the following objects are given: @Point; WolorPoint; PI Cl ->P2. Equal; CI Cl -X2. Equal ; ClCl->Pl.Equal; Pi []-X2. Equal; -> eq; In objects of the subclass ColorPoint the ThisClass is ColorPoint. This implies that the qualification of the parameter P is then ColorPoint, so Equal may slso be extended to test for the equality of the c attribute of ColorPoint objects. This can also be expressed as: C qual(object(ThisObject)) It should be noted that the notion of ThisClass may be obtained as a virtual class. In Point it would be defined as a virtual class, e.g. 148 Point it would be extended by extended thisClass: PI,P2: CI,C2: #I qual(object(P)) and in ColorPoint class See [9] for a further discussion of this. The above example illustrate the same problem as discussed in section 6. This time the construction ThisClass is the cause of qualification strengthening. The foIlowing examples of use of the classes will iIIustrate the type-checking problems. PC1 enter : virtual thisclass c53 cs3 c73 C83 Here it is known at compile-time that Pi and P2 refer to instances of Point and that Cl and C2 refer to instances of ColorPoint. In other words Pl,P2,Cl and C2 are constant references. The effect is that case 5-8 can be statically type-checked. Case 5-7 wiII pass but in case 8 there will be found a type-error. Recalling the three different solutions to this typechecking problem we wiU find the following: 1. Not allowing strengthened. Adopting ColorPoint ECOOP/OOPSLA ‘90 Proceedings the type-demands to be this attitude, the definition of class is wrong since it is strengthening the October 21-25, 1990 [2] W. 12. Cook: A Proposal for Making Eifiel Type Safe, In ECOOP’89, European Conference on Object-Oriented Programming, Cambridge University Press 1989. [3] O.-J, Dahl and K. Nygaard: Sin&a-67 Common Base &Language Publication S-22, Norwegian Computing Center, Oslo 70, 72, 84. Current version: Programming Language - Simula, Swedish Standard SS.63.61.14, ISBN-91-7162-2349 [4] O.-J. Dahl the Simula Languages, New York, and K. Nygaard: The Development of Languages, In History of Programming ed. R. W. Wexelblat, Academic Press, 1981. In OOPSLA/ECOOP’90, Object-Oriented Programming Systems, Languages and Applications, Sigplan Notices, 1990. [14] C. Schaffert et. al: An Introduction to Trellis/Owl, In OOPSLA86, Object-Oriented Programming Systems, Languages and Applications, Sigplan Notices, Vol. 21, No. 11, Nov. 1986. [15] B. Stroustrup: guage, Addison The C-i-+ Programming Wesley, 1986. [16] N. Wirth : The Programming Language Pascal, Acta Informatica 1, 19’71, 35-63. [5] A. Goldberg and D. Robson: Smalltalk-8U: The Language and its Implementation, AddisonWesley Publishing Company, 1983. [6] B.B. Kristensen, O.L. Madsen, B. M$IIerPedersen, K. Nygaard: Syntaz Directed PTOgram ModulaTization, In: Interactive Computing Systems (ed. P. Degano, E. SandewaII), NorthHolland, 1983 [7] B.B. Kristensen, O.L. Madsen, B. M@IIerPedersen: The BETA Progmmming Language, In: B.D. Shriver, P.Wegner (ed.), Research Directions in Object-Oriented Programming, MIT Press, 1987. [8] O.L. Madsen and B. Moller-Pedersen: What Object-oriented Programming may be and what it does not have to be In ECOOP’88, European Conference on Object-Oriented Programming, Lecture Notes In Computer Science, Vol. 322, Springer Verlag, 1988. [9] O.L. Madsen and B. M@I.ler-Pedersen: Virtual Class,rs - A Powerful Mechanism in ObjectOriented Programming, In OOPSLA’89, ObjectOriented Programming Systems, Languages and AppIil:ations, Sigplan Notices, Vol. 24, No. 10, Oct. 3.989 [lo] B. Ml:yer: Object Oriented tion, I?rentice-HaII 1988 [ll] Software Construc- B. MI:yer: Static Typing for Eiffel. Interactive Softw.kre Engineering Inc., July 2, 1989 [12] K. Nygaard: Basic Concepts in Object Oriented Prognzmming, Sigplan Notices, Vol; 21, No. 10, 128-132 (October 1986). [13] J. Palsberg. M. I. Schwartzbach: Polymorphism for Object-Oriented October 21-25, 1990 Lan- Substitution Programming, ECOOP/OOPSLA ‘90 Proceedings 149 demands on the parameter P of the procedure Equal (and Move as well). This is the attitude taken in [l]. It has the effect that the above and many other programs will be illegal. In [l] it is phrased slightly different, the two classes Point and ColorPoint are found not to be type compatible. 2. Introducing references that are type exact. The effect of this possibility is shown above using part objects. All the expressions above are statically checkable. If this is the only alternative we can not write general code managing ColorPoints (and possibly many other sub classes) as Points which is of great practical value. Thii is also the effect of the suggested restriction for EifFel [ll]. The proposal in [13] for homogeneous variables is another example of this. Although type exact variables is a useful mechanism in many situations, we find it a too strong restriction to be the general case. To conclude this discussion we finally also show the BETA formulation of one other example in Cl]. Test: proc (a X,Y: - Point; enter(XCl,Yl3) X Cl ->Y . Equal Ca run-time exit #> ; check) Since it is not statically known whether or not X and Y refer to instances of Point or ColorPoint it is necessary to perform a run-time check in the call X [I ->Y . Equal. If X refers to a ColorPoint, then Y must also refer to an instance of ColorPoint or: qual(object(X)) C qual(object(Y).Equal.P) The procedure Test may be called in the following (PlCl,Pri3)->Test Cl3 (Cl Cl ,Cr Cl )->Test (ClCl ,PrCl)->Test (PlCl ,CrCl)->Test w c31 (41 way: In none of these cases there is a need for a run-time check at the call since the procedure Test only requires its arguments to be of class Point. The execution of Test will in all cases perform a run-time test when executing X [] ->Y . Equal. In the fourth case the run-time test in procedure Test will fail since the class of X (Pl) is Point, while the the relation will demand at least a ColorPoint (again assuming that Pl is denoting a Point object). Again using the first solution only case 1 will be accepted by the compiler since ColorPoint is not considered a subclass of Point. This would require the 150 View publication stats programmer to write one version of the Test procedure for each combination of argument types. The second solution would lead to exactly the same situation since the pointers X and Y would be considered to have exact qualification match. 8 Conclusion Issues regarding typing and programming language design have been the subject of this paper. Programming is regarded as modeling a real or imaginary part of world. From this point of view we conclude that the most important feature of the class mechanism is the ability to model concepts. Subclassing models specialization and inheritance of properties from the application domain, Used in this way, the class hierarchy defines a type system that is understandable in terms of the application domain. A type system based on the class concept has been described together with a discussion of how strong typing can be supported in a flexible way. The strength of a type system is regarded as the amount of information conveyed with the type of an expression. This information can be used for compile-time type checking and early error reporting. Several examples are given of weakening and strengthening the type of an expression. It is argued that a certain amount of such flexibility is needed in order to support different levels of abstraction. Type strengthening expressions gives rise to run-time type checking. The problems arising when classes are parameterized with types (i.e. classes) have been analyzed and it has been shown that the traditional approach with run-time checks in certain situations can also be used here. It has also been discussed how the amount of run-time checks can be decreased and even completely removed by introducing certain restrictions in the language (type exact references, forbidding type strengthening). Finally it has been argued that these restrictions can be very useful in many situations, but only allowing these restricted cases will hamper the expressiveness of the language. Acknowledgement. We have received many useful comments from colleagues, stu’dents and the anonymous referees. References [l] P.S. Canning, W.R. Cook, W.L. Hill, W.G. Olthoff: InteTj’aces for Strongly- Typed ObjectOriented Programming, In OOPSLA’89, ObjectOriented Programming Systems, Languages and Applications, Sigplan Notices, Vol. 24, No. 10, Oct. 1989 ECOOPlOOPSLA‘90 Proceedings October 21-25, 1990