In Figure 9 we show parts of
a class with a method
with type Func(): PointType. That is, it is a parameterless
procedure that returns a point representing the center of the circle.
Suppose
is a subtype of
.Then a
subclass could redefine
to have type
Func(): ColorPointType, thus returning a colored point for the center
of the circle.
On the other hand, if
class also has a method
with type
then we may not
change its type in
to
.Because
occurs in a contravariant (parameter) position in the type of
, it may only be replaced by a supertype, which would not be
of much use here. If, however, we dropped
in favor
of the procedure
, whose only parameters are integers, then
no difficulty arises with the subclass.
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 is actually a value of type
. 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 example, if
has an instance
variable
of type
, we may not replace its
type by
in the subclass. Instead, we must add a new
instance variable
with type
,
so that
will have
and
instance variables. The body of the
redefined
method in
(which does return an
element of type
) will have to create a color point value
from the values in
and
. 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 is a subclass of
, we want
in
to have type Func(): CType, while it
should have type Func(): SCType in
. 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 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
just in order to change the result type. We also cannot
simply include a call of
from the corresponding
method of
, as it returns a value of type
, which is a
supertype of what is needed. Thus we would have to rewrite
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 /
example from the previous section. The problem is that even if
were a subtype of
,
the method
has type
in
and type
in
. 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
is changed, while the type of
the parameter of method
is not, a type error will
result. Thus, if we wish to make covariant changes to the parameter
type of a method
, 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
/
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 example.
The cost of providing this support for covariance is that subclasses will
no longer always generate subtypes.