Suppose we have a variable of type
(i.e., an expression of
type
) that we wish to have masquerade as a variable of
type
. See Figure 3. It is common to think of a
variable of type
as having two values: an L-value and an
R-value. The L-value is the location corresponding to the variable, while
the R-value is the value of type
actually stored there.
Variables can be used in both value-supplying and value-receiving contexts.
A value-supplying context is one that requires a value of type
(i.e., the R-value of the variable).
This is illustrated in the figure by the operation (arrow) labeled
``val'' coming out
of the variable (representing the R-value of the variable).
By the definition of subtype, in order for a variable
of type
to be able to
masquerade as a value of type
in all contexts of this kind, we
need
. This should be clear from the right-hand
diagram in the figure, where in order for
to provide a compatible value
using the
operator,
.
A value-receiving context is one in which a variable of
type is assigned to, e.g., a
statement of the form
, for
an expression of
type
. This is illustrated in the figure by an arrow labeled
``:='' going into the variable.
In this context we will be interpreting the variable as a
reference or location (i.e., the L-value) in which to store a value.
We have already seen above
that an assignment like this is type safe if
has a type that
is a subtype of the type of the variable
.
Thus if we wish to use a variable of type
in all contexts of this
form, we must ensure that
. Again this should be
clear from the right-hand diagram in the figure.
Thus for a variable of type to masquerade as a variable of
type
in value-supplying contexts we must have
, while its use in value-receiving contexts require
. It follows that there are no non-trivial subtypes of variable
(reference) types. Thus,
Another way of understanding the behavior of reference and function types under subtyping is to consider the different roles played by suppliers and receivers of values. Any slot in a type expression that corresponds to a supplier of values must have subtyping behave covariantly (the same direction as the full type expression), while any slot corresponding to a receiver of values must behave contravariantly (the opposite direction). Thus L-values of variables and parameters of functions, both of which are receivers of argument values, behave contravariantly with respect to subtyping. On the other hand, the R-values of variables and the results of functions, both of which are suppliers of values, behave covariantly. Because variables have both behaviors, any changes in type must be simultaneously contravariant and covariant. Hence subtypes of reference types must actually be equivalent.
The subtyping relation among object types can be rather subtle. In the simple type system considered in section 3, object types are considered subtypes when the record of method types of the subtype extends that of the supertype. That is, a subtype may contain more methods than the supertype, but the corresponding methods must have the same types. This is more restrictive than the definition of subtype for record types given above. We will see in section 4 that there is no need for such a restrictive notion of subtyping.