Our language of classes favor expressiveness while retaining type soundness
and modularity. Thus, several other design choices, which could allow
significant simplifications or enforce stronger invariants, can be derived
from our proposal either by small variations or restrictions.
Usually, object initialization is defined at the class level, rather than at
the object level, which is hidden to users. Instead, in our proposal
objects are visible because they provide the basic semantics.
Regarding the object initialization, we enriched the object language
as little as needed by splitting names into private and public. Hereafter,
we illustrate a more standard approach to object initialization by means
of straightforward translation of a higher-level language. (Indeed,
examples of Section 4 conform with this approach.)
In the user-language we replace class and obj declarations by
the following ones:
Class definitions are written
classc(x) = ICinP where
the name c of the class takes a list of arguments (x);
I is a list of inheritance clauses of the form
( inheritsci(ui) asdi)i Î I where
each ci is a previsouly defined class and
di a local name for the body of ci.
C is as before, except that it contains exactly one rule of the form
cinit(x)|> Q.
The names cinit are private and special in the sense that they cannot
occur (in the user-language) anywhere else. Indeed, cinit play the role
of class constructors.
P is as before.
Object definitions are written objx = c(u) inP where c has been
defined as above.
The translation of the above declarations are, respectively:
classc(x) =
matchCwithcinit(x) Þ
cinit(x) &i Î Iciinit (ui)
endinP
objx = c \ initcinit(u) inP.
In this user language, the class is responsible for object initialization.
Moreover, by constructions, subclasses always invoke the initialization
methods of its parent classes.
Indeed, other design choices are possible. For instance, this design
easily generalizes to allow multiple class constructors.
The examples in Section 4 only use selective
refinement in a restrictive and simple form. In particular, refinement
clauses K Þ K' |> P always uses K with 0 or 1 message.
Restricting to such cases simplifies rule Filter-Apply in the
rewriting semantics (Figure 5) and the static semantics of
classes given below in Section 10.
A different approach has been taken in Polyphonic C [3].
This language extends C with reaction rules a la join
calculus (called chords).
As inheritance is concerned, Polyphonic C is quite severe with respect
to join patterns: if a method is overridden, then all join patterns
concerning that method must be overridden, and so on, transitively.
This apporach is significantly different than ours. In particular, while we
allow overriding of as few methods as possible during inheritance, they
instead require overriding of too many methods. For instance, in the
common pattern where every method synchronizes with a global object state,
overriding any method would require overriding all of them.