next up previous contents
Next: Introducing MyType Up: Toward more flexible type Previous: Subtyping of method types

Examples using flexible types

A few examples will help illustrate how we can use this extra flexibility, as well as where further flexibility is needed.

In Figure 9 we show parts of a ${\it {Circle}}$ class with a method ${\it {getCenter}}$ with type Func(): PointType. That is, it is a parameterless procedure that returns a point representing the center of the circle. Suppose ${\it {ColorPointType}}$ is a subtype of ${\it {PointType}}$.Then a ${\it {ColorCircle}}$ subclass could redefine ${\it {getCenter}}$ to have type Func(): ColorPointType, thus returning a colored point for the center of the circle. On the other hand, if ${\it {Circle}}$ class also has a method ${\it {
changeCenter}}$ with type ${\it {Proc(PointType)}}$ then we may not change its type in ${\it {ColorCircle}}$ to ${\it {Proc(ColorPointType)}}$.Because ${\it {PointType}}$occurs in a contravariant (parameter) position in the type of ${\it {
changeCenter}}$, it may only be replaced by a supertype, which would not be of much use here. If, however, we dropped ${\it {
changeCenter}}$ in favor of the procedure ${\it {move}}$, whose only parameters are integers, then no difficulty arises with the subclass.


  
Figure 9: Circle and ColorCircle classes with more flexible types.
\begin{figure*}
\begin{verbatim}
class Circle
 var
 center = OrigPoint: PointTyp...
 ... -- illegal change of type 
 begin ... end
end class;\end{verbatim}\end{figure*}

While the replacement of method types by subtypes in subclasses solves some typing problems, it does not solve them all.

Unfortunately, we do not have the same flexibility in changing the types of instance variables as we do with methods. Recall from the previous section that a variable whose declared type is ${\it {T}}$ is actually a value of type ${\it {{\rm{\it ref}}\ T}}$. As discussed in section 2.1, reference types have no subtypes because they can be used in either value-receiving (on the left side of :=) or value-supplying positions. As a result, it is not possible to change the types of instance variables in subclasses.

Returning to our ${\it {Circle}}$ example, if ${\it {Circle}}$ has an instance variable ${\it {center}}$ of type ${\it {PointType}}$, we may not replace its type by ${\it {ColorPointType}}$ in the subclass. Instead, we must add a new instance variable ${\it {color}}$ with type ${\it {ColorType}}$, so that ${\it {ColorCircle}}$ will have ${\it {center}}$ and ${\it {color}}$ instance variables. The body of the redefined ${\it {getCenter}}$ method in ${\it {ColorCircle}}$ (which does return an element of type ${\it {ColorPointType}}$) will have to create a color point value from the values in ${\it {center}}$ and ${\it {color}}$. This is not as convenient as we might like, but it does allow us to use inheritance where the more rigid simple typing discipline did not.

This ability to replace result types by subtypes helps us out with the clone example as well, since if ${\it {SC}}$ is a subclass of ${\it {C}}$, we want ${\it {DeepClone}}$ in ${\it {C}}$ to have type Func(): CType, while it should have type Func(): SCType in ${\it {SC}}$. Because this is a covariant change in the result parameter, this is a legal change in this more flexible system.

However, there is still a remaining problem here as well. We would occasionally like to inherit ${\it {DeepClone}}$ in a subclass without change (for example, if we add or override methods without adding new instance variables). However in order to get its type to change appropriately, we would have to override ${\it {DeepClone}}$ just in order to change the result type. We also cannot simply include a call of ${\it {super.DeepClone()}}$ from the corresponding method of ${\it {SC}}$, as it returns a value of type ${\it {CType}}$, which is a supertype of what is needed. Thus we would have to rewrite ${\it {DeepClone}}$ from scratch in such situations. The changes suggested in the next section will allow us to specify that the result type should change automatically in subclasses, even if we do not override the method.

Finally, notice that we still are unable to write the ${\it {Node}}$ / ${\it {DoubleNode}}$ example from the previous section. The problem is that even if ${\it {DoubleNodeType}}$ were a subtype of ${\it {NodeType}}$[*], the method ${\it {setNext}}$ has type ${\it {Proc(NodeType)}}$ in ${\it {Node}}$ and type ${\it {Proc(DoubleNodeType)}}$ in ${\it {DoubleNode}}$. Because the type of the parameter should vary contravariantly, not covariantly, this change of type would be illegal in the subclass. In particular, if the type of the parameter of method ${\it {setNext}}$ is changed, while the type of the parameter of method ${\it {attachRight}}$ is not, a type error will result. Thus, if we wish to make covariant changes to the parameter type of a method ${\it {m}}$, we may have to examine the bodies of all other methods of the class (including those which are inherited) to identify which depend on the one being changed. We may then be required to make appropriate changes to those methods in order to preserve type safety. (But don't despair yet. In the next two sections we will see how to handle uniformly some important special cases, including the ${\it {Node}}$ / ${\it {DoubleNode}}$ example.)

As noted earlier, the new draft standard C++ proposal loosens the rules of C++ to allow covariant changes to the result types of methods that are redefined in subclasses. There is no similar proposal to allow contravariant changes to parameter types, presumably because, though safe, there are few cases in which this would actually be helpful.

Eiffel, on the other hand, does allow covariant changes to both instance variables and parameter types of methods in subclasses. This decision has provoked great controversy. (See [Coo89] for an early proposal to fix the type system.) The arguments boil down to the following. On the one hand, proponents argue that covariance for function parameter types is often useful, and that actual run-type errors rarely result in practice. The counter-argument is that unrestricted covariance for parameter types and instance variables is unsound. Both sides are correct. One of the major problems in designing static type systems for object oriented programming languages has been to design sound typing rules that support the examples where covariance appears to be necessary.

In the next section we will introduce a new key word, MyType, which can be used inside classes to stand for the type of self. The use of this keyword will allow us to provide for type-safe covariant changes to parameter and instance variables in many important cases, including the ${\it {DoubleNode}}$ example. The cost of providing this support for covariance is that subclasses will no longer always generate subtypes.


next up previous contents
Next: Introducing MyType Up: Toward more flexible type Previous: Subtyping of method types
Kim Bruce
10/28/1998