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.