Previous Table of Contents Next


Consider the following declarations:

   TYPE
     A = OBJECT a: INTEGER; METHODS p() END;
     AB = A OBJECT b: INTEGER END;
     PROCEDURE Pa(self: A) = ... ;
     PROCEDURE Pab(self: AB) = ... ;

The procedures Pa and Pab are candidate values for the p methods of objects of types A and AB:

   TYPE T1 = AB OBJECT OVERRIDES p := Pab END

declares a type with an AB data record and a p method that expects an AB.T1 is a valid subtype of AB. Similarly,

   TYPE T2 = A OBJECT OVERRIDES p := Pa END

declares a type with an A data record and a method that expects an A. T2 is a valid subtype of A. A more interesting example is

   TYPE T3 = AB OBJECT OVERRIDES p := Pa END

which declares a type with an AB data record and a p method that expects an A. Because every AB is an A, the method is not too choosy for the objects in which it will be placed. T3 is a valid subtype of AB. In contrast,

   TYPE T4 = A OBJECT OVERRIDES p := Pab END

attempts to declare a type with an A data record and a method that expects an AB; because not every A is an AB, the method is too choosy for the objects in which it would be placed. The declaration of T4 is a static error.

The following example illustrates the difference between declaring a new method and overriding an existing method. After the declarations

   TYPE
     A = OBJECT METHODS m() := P END;
     B = A OBJECT OVERRIDES m := Q END;
     C = A OBJECT METHODS m() := Q END;
   VAR
     a := NEW(A); b := NEW(B); c := NEW(C);

we have that

a.m() activates P(a)
b.m() activates Q(b)
c.m() activates Q(c)

So far there is no difference between overriding and extending. But c’s method suite has two methods, whereas b’s has only one, as can be revealed if b and c are viewed as members of type A:

NARROW(b, A).m() activates Q(b)
NARROW(c, A).m() activates P(c)

Here, NARROW is used to view a variable of a subtype as a value of its supertype. It is more often used for the opposite purpose when it requires a runtime check.

The last example uses object subtyping to define reusable queues. First the interface:

   TYPE
         Queue = RECORD head, tail: Queue Elem END;
         QueueElem = OBJECT link: QueueElem END;
     PROCEDURE Insert (VAR q: Queue; x: QueueElem);
     PROCEDURE Delete (VAR q: Queue): QueueElem;
     PROCEDURE Clear (VAR q: Queue);

Then a sample client:

   TYPE
     IntQueueElem = QueueElem OBJECT val: INTEGER END;
   VAR
     q: Queue;
     x: IntQueueElem;
      ...
     Clear(q);
     x := NEW(IntQueueElem, val := 6);
     Insert(q, x);
     ...
     x := Delete(q)

Passing x to Insert is safe because every IntQueueElem is a QueueElem. Assigning the result of Delete to x cannot be guaranteed valid at compile time because other subtypes of QueueElem can be inserted into q, but the assignment will produce a checked runtime error if the source value is not a member of the target type. Thus IntQueueElem bears the same relation to QueueElem as [0..9] bears to INTEGER.

11.4.10. Subtyping Rules

We write T <: U to indicate that T is a subtype of U and U is a supertype of T.

If T <: U, then every value of type T is also a value of type U. The converse does not hold: For example, a record or array type with packed fields contains the same values as the corresponding type with unpacked fields, but there is no subtype relation between them. This section presents the rules that define the subtyping relation.

For ordinal types T and U, we have T <: U if they have the same basetype and every member of T is a member of U. That is, subtyping on ordinal types reflects the subset relation on the value sets.

For array types,

   (ARRAY OF)m ARRAY J1 OF ... ARRAY Jn OF ARRAY K1 OF ... ARRAY Kp OF T
        <:
         (ARRAY OF)m (ARRAY OF)n ARRAY I1 OF ... ARRAY Ip OF T
     if NUMBER(Ii) = NUMBER(Ki) for i = 1, ..., p.

That is, an array type A is a subtype of an array type B if they have the same ultimate element type and the same number of dimensions, and for each dimension, either both are open (as in the first m dimensions), or A is fixed and B is open (as in the next n dimensions), or they are both fixed and have the same size (as in the last p dimensions).

For reference types,

	NULL <: REF T <: REFANY
	NULL <: UNTRACED REF T <: ADDRESS

That is, REFANY and ADDRESS contain all traced and untraced references, respectively, and NIL is a member of every reference type. These rules also apply to branded types.

For procedure types,

	NULL <: PROCEDURE(A): R RAISES S for any A, R, and S.

That is, NIL is a member of every procedure type.

	PROCEDURE(A): Q RAISES E <: PROCEDURE(B): R RAISES F
	if signature (B): R RAISES F covers signature (A): Q RAISES E.

That is, for procedure types, T <: U if they are the same except for parameter names, defaults, and the raises set, and the raises set for T is contained in the raises set for U.

For object types,

	ROOT <: REFANY
	UNTRACED ROOT <: ADDRESS
	NULL <: T OBJECT ... END <: T

That is, every object is a reference, NIL is a member of every object type, and every subtype is included in its supertype. The third rule also applies to branded types.

For packed types,

   BITS n FOR T <: T

and

   T <: BITS n FOR T

That is, BITS FOR T has the same values as T.

Finally, for all types,

   T <: T for all T
   T <: U and U <: V implies T <: V for all T, U, V.

That is, <: is reflexive and transitive.


Previous Table of Contents Next