We have not yet discussed what type should be assigned to for the purposes of static typing. Suppose we have a class with a method , whose body contains one or more occurrences of . Recall that occurrences of in the bodies of methods refer to the receiver of the message. Languages with simple type systems like C++, Java, Object Pascal, and Modula-3 presume that has the same type as the objects generated from the class being defined. By our conventions, this would mean we would assume that has the type in order to type check .
However, look at what happens when we define a subclass of . If is inherited without change, it is roughly equivalent to copying the code in the body of into . Thus, the meaning of changes to denote an element of type . However, type checking the body of under the assumption that the type of is is weaker than assuming its type is as long as is a subtype of . That is, if is a subtype of and type checks under the assumption that self has type , then it is guaranteed to type check under the (stronger) assumption that self has type . Thus as long as subclasses always generate subtypes (as is the case in the simple type discipline described in section 3), we will not have difficulty with inherited methods becoming type-unsafe in subclasses.
Returning to the example in Figure 7, suppose we find a way to write as a subclass of . As remarked earlier, should be a binary method. That is, its argument should be of the same type as the object it is sent to. Thus while has a parameter of type in class , it ought to have a parameter of type in subclass . But then, because of contravariance, will not be a subtype of . Nevertheless it makes perfectly good sense to define via inheritance from since most of the code of corresponding methods is identical.
Thus if we do manage to increase expressiveness of the language in order to write these subclasses that we were previously barred from writing, then subclasses will no longer generate subtypes. As a result we will have to be more careful in type-checking methods involving .It will no longer be sufficient to type check methods by assuming that the type of self has the same type as the objects generated from the class.
In order to be able to handle self more accurately in type checking, we introduce the keyword, MyType, for its type. The key idea here is that MyType will play two different but related roles in describing the types of objects. In the typing of a particular object, the type expression MyType can simply be seen as another name for the type of the object. However, when we write the code for methods in a class, we will do it with the understanding that the meanings of self and MyType are variable and will change in tandem within subclasses.
We may think of self and MyType as abbreviations for recursive definitions of the object and its type. In fact, much of the early work on the semantics and typing of object-oriented languages was done in the context of recursively defined records of methods. Several early papers reflecting this approach are included in [GM94].
The use of MyType provides for the smooth change of method types in subclasses, as is desired for clone operations. For example, we may specify the type of the and methods to be Func(): MyType. If we send either of the messages or to an object of type , the result will be of the same type, , as MyType stands for the name of the type of the object executing the method.
The use of MyType also solves our typing problems with . We have rewritten the example from Figure 7 in Figure 10, replacing all occurrences of by MyType. We have also used MyType to specify the type of the instance variable .
We now define as a subclass of in Figure 11. The use of MyType in the types of instance variables and methods ensures that all occurrences will change uniformly in the subclass.
Language constructs giving the type of self an explicit name exist in Trellis/Owl [SCB+86] and Eiffel [Mey88,Mey92]. In Eiffel, the name for the type of self is a special instance of a more general construct that allows the programmer to use an expression of the form , for an identifier. This construct evaluates at compile time to the static type of . Because self in Eiffel is written , MyType is written .
While we have shown that the use of MyType makes it possible to write cloning methods and allows us to write as a subclass of , we have not yet discussed how methods involving self and MyType can be type checked. While self has type MyType, what are we allowed to assume about MyType, when type checking methods? If we type check under the assumption that MyType is the same as the type of the objects generated by the class (e.g., type check the methods of under the assumption that MyType is the same as ), how can we be certain that the inherited methods will still be type correct in the subclass, where MyType will have a different meaning? In the next section we address this question.