next up previous contents
Next: The matching relation between Up: Typing in object-oriented languages: Previous: Examples using flexible types

Introducing MyType

  The improved flexibility in allowing changes to types of methods in subclasses introduced in the previous section allowed some improvement in the type-checking capabilities of our object-oriented language, but it did not help with our node or cloning examples. Providing more accurate information on the type of ${\it {
self}}$ will turn out to be the key to overcoming these difficulties.

We have not yet discussed what type should be assigned to ${\it {
self}}$ for the purposes of static typing. Suppose we have a class ${\it {C}}$ with a method ${\it {m}}$, whose body contains one or more occurrences of ${\it {
self}}$. Recall that occurrences of ${\it {
self}}$ 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 ${\it {
self}}$ has the same type as the objects generated from the class being defined. By our conventions, this would mean we would assume that ${\it {
self}}$ has the type ${\it {CType}}$ in order to type check ${\it {m}}$.

However, look at what happens when we define a subclass ${\it {SC}}$ of ${\it {C}}$. If ${\it {m}}$ is inherited without change, it is roughly equivalent to copying the code in the body of ${\it {m}}$ into ${\it {SC}}$.[*] Thus, the meaning of ${\it {
self}}$ changes to denote an element of type ${\it {SCType}}$. However, type checking the body of ${\it {m}}$ under the assumption that the type of ${\it {
self}}$ is ${\it {CType}}$ is weaker than assuming its type is ${\it {SCType}}$ as long as ${\it {SCType}}$ is a subtype of ${\it {CType}}$. That is, if ${\it {SCType}}$ is a subtype of ${\it {CType}}$ and ${\it {m}}$ type checks under the assumption that self has type ${\it {CType}}$, then it is guaranteed to type check under the (stronger) assumption that self has type ${\it {SCType}}$. 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 ${\it {Node}}$ example in Figure 7, suppose we find a way to write ${\it {DoubleNode}}$ as a subclass of ${\it {Node}}$. As remarked earlier, ${\it {setNext}}$ should be a binary method. That is, its argument should be of the same type as the object it is sent to. Thus while ${\it {setNext}}$ has a parameter of type ${\it {NodeType}}$ in class ${\it {Node}}$, it ought to have a parameter of type ${\it {DoubleNodeType}}$ in subclass ${\it {DoubleNode}}$. But then, because of contravariance, ${\it {DoubleNodeType}}$ will not be a subtype of ${\it {NodeType}}$. Nevertheless it makes perfectly good sense to define ${\it {DoubleNode}}$ via inheritance from ${\it {Node}}$ 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 {
self}}$.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 ${\it {ShallowClone}}$ and ${\it {DeepClone}}$ methods to be Func(): MyType. If we send either of the messages ${\it {ShallowClone()}}$or ${\it {DeepClone()}}$ to an object of type ${\it {T}}$, the result will be of the same type, ${\it {T}}$, 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 ${\it {Node}}$. We have rewritten the ${\it {Node}}$ example from Figure 7 in Figure 10, replacing all occurrences of ${\it {NodeType}}$ by MyType. We have also used MyType to specify the type of the instance variable ${\it {next}}$.

Figure 10: Node class with MyType.
class Node
 value = 0: Integer;
 next = ni...
end class;\end{verbatim}\end{figure*}

We now define ${\it {DoubleNode}}$ as a subclass of ${\it {Node}}$ 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.

Figure 11: Doubly-linked node class.
class DoubleNode inherits Node modifying attach...
end class;\end{verbatim}\end{figure*}

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 ${\it {like\ x}}$, for ${\it {x}}$ an identifier. This construct evaluates at compile time to the static type of ${\it {x}}$. Because self in Eiffel is written ${\it {Current}}$, MyType is written ${\it {
like\ Current}}$.

While we have shown that the use of MyType makes it possible to write cloning methods and allows us to write ${\it {DoubleNode}}$ as a subclass of ${\it {Node}}$, 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 ${\it {Node}}$ under the assumption that MyType is the same as ${\it {NodeType}}$), 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.

next up previous contents
Next: The matching relation between Up: Typing in object-oriented languages: Previous: Examples using flexible types
Kim Bruce