An object is either NIL
or a reference to a data record paired
with a method suite, which is a record of procedures that will accept the
object as a first argument.
An object type determines the types of a prefix of the fields of the data
record, as if ``OBJECT
'' were ``REF RECORD
''. But in the case
of an object type, the data record can contain additional fields introduced by
subtypes of the object type. Similarly, the object type determines a prefix
of the method suite, but the suite can contain additional methods introduced
by subtypes.
If o
is an object, then o.f
designates the data field named
f
in o
's data record. If m
is one of o
's methods,
an invocation of the form o.m(...)
denotes an execution of o
's
m
method. An object's methods can be invoked, but not read or written.
If T
is an object type and m
is the name of one of T
's
methods, then T.m
denotes T
's m
method. This notation
makes it convenient for a subtype method to invoke the corresponding method of
one of its supertypes.
A field or method in a subtype masks any field or method with the same name in
the supertype. To access such a masked field, use NARROW
to view the
subtype variable as a member of the supertype, as illustrated below.
Object assignment is reference assignment. Objects cannot be dereferenced, since the static type of an object variable does not determine the type of its data record. To copy the data record of one object into another, the fields must be assigned individually.
There are two predeclared object types:
ROOT
The traced object type with no fields or methods UNTRACED ROOT
The untraced object type with no fields or methods
The declaration of an object type has the form:
TYPE T = ST OBJECT Fields METHODS Methods OVERRIDES Overrides ENDwhere
ST
is an optional supertype, Fields
is a list of field
declarations, exactly as in a record type, Methods
is a list of
method declarations and Overrides
is a list of method
overrides. The fields of T
consist of the fields of ST
followed by the fields declared in Fields
. The methods of T
consist of the methods of ST
modified by Overrides
and followed
by the methods declared in Methods
. T
has the same reference
class as ST
.
The names introduced in Fields
and Methods
must be distinct from
one another and from the names overridden in Overrides
. If ST
is omitted, it defaults to ROOT
. If ST
is untraced, then the
fields must not include traced types. (This restriction is lifted in unsafe
modules.) If ST
is declared as an opaque type, the declaration of
T
is legal only in scopes where ST
's concrete type is known to
be an object type.
The keyword OBJECT
can optionally be preceded by ``BRANDED
'' or
by ``BRANDED b
'', where b
is a text constant. The meaning is
the same as in non-object reference types.
A method declaration has the form:
m sig := procwhere
m
is an identifier, sig
is a procedure signature, and
proc
is a top-level procedure constant. It specifies that T
's
m
method has signature sig
and value proc
. If
``:= proc
'' is omitted, ``:= NIL
'' is assumed. If proc
is non-nil, its first parameter must have mode VALUE
and type some
supertype of T
, and dropping its first parameter must result in a
signature that is covered by sig
.
A method override has the form:
m := procwhere
m
is the name of a method of the supertype ST
and
proc
is a top-level procedure constant. It specifies that the m
method for T
is proc
, rather than ST.m
. If proc
is non-nil, its first parameter must have mode VALUE
and type some
supertype of T
, and dropping its first parameter must result in a
signature that is covered by the signature of ST
's m
method.
Examples. 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.
For example:
TYPE T1 = AB OBJECT OVERRIDES p := Pab ENDdeclares 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 ENDdeclares 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 ENDwhich declares a type with an
AB
data record and a p
method that
expects an A
. Since 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 ENDattempts to declare a type with an
A
data record and a method that
expects an AB
; since 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, while b
's has only one, as can
be revealed if b
and c
are viewed as members of type A
:
Here
NARROW(b, A).m()
activatesQ(b)
NARROW(c, A).m()
activatesP(c)
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: QueueElem 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 an example 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, since every IntQueueElem
is
a QueueElem
. Assigning the result of Delete
to x
cannot
be guaranteed valid at compile-time, since 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
.
m3-support@elego.de