Inheritance in Grace 0.7 uses trait-style aliasing to invoke overridden inherited methods. For example, a pedigree cat class can inherit from a cat class and provide a new definition for (that is, override) its miaow
method. Writing:
|
class pedigreeCat(aName) colour(aColour) { inherit cat(aName) colour("Pedigree " ++ aColour) alias catMiaow = miaow var prizes := 0 method winner {prizes := prizes + 1} method miaow is override { catMiaow ++ "and won {prizes} prizes" } } |
declares a new class whose instances contain all the methods from the inherited object, and also a new variable prizes
, a new method winner
, and an overriding version of the miaow
method. The trick, of course, is that the pedigreeCat’s miaow method needs to be able to request the inherited miaow
method. This is arranged by a new alias clause in the inherits statement:
|
inherit cat(aName) colour("Pedigree " ++ aColour) alias catMiaow = miaow |
The alias clause makes the superclasses method miaow
available under the new name. This lets our new, overriding definition of miaow
re-use the inherited behaviour by requesting the catMiaow
alias.
* * * * *
The main reasons for this change are that super is a complicated construct, frequently misunderstood by students and even (dare we say it) by some instructors. The syntax super.methodName
and self.methodName
are confusing. They differ not in the receiver, as the syntax implies, since both statements send the request to self. No, the difference is in the rule used to find the correct method once the receiver has the request. Encapsulation says that this should not be any of the requestor’s business—yet with super, it is. Moreover, the meaning of a super-request depends on the location of the code that is making it; this is a clear violation of referential transparency.
Aliasing is simpler. Aliasing simplifies the operational semantics of Grace, by removing super-requests from the language. At the same time, aliasing is more powerful, since it allows reuse of methods from any ancestor in the inheritance chain. Aliasing also simplifies the implementation. Moreover, aliasing supports inheritance from a composition of traits: in contrast, the meaning of super is problematic if methods with the requested name are inherited from multiple places.
Abolishing super reduces the number of syntactically-different requests to four: (1) explicit requests (o.x
), (2) self requests (self.x
), (3) outer requests (outer.x
), and (4) implicit requests (x
), which resolve either to self requests or to outer requests.
Aliasing also supports a simpler definition of inheritance in terms of “prefixing”, which goes back to Simula. The idea is that a series of inheriting class declarations can be consider as equivalent to a single class declaration in which the bodies of the superclasses are prefixed to the declaration of the subclass.
|
class top { method a { … } } class middle { inherit top method b { … } } class bottom { inherit middle method c { … } } |
|
|
class bottom { method a { … } method b { … } method c { … } } |
|
In this example, the behaviour of the bottom
class should be the same whether it was declared with inheritance (on the left) or without (on the right). In Grace, of course, this textual prefixing works only when all the declarations are in the same lexical scope, i.e., when they are declared one after another in the same file.
An alias clause adds an method additional declaration (for the new name) into the inheriting object; an overridden declaration is simply omitted.
|
class top { method a { body_t } } class middle { inherit top alias topA = a method a is override { body_m } class bottom { inherit middle method c { … } } |
|
|
class bottom { method topA { body_t } method a { body_m } method c { … } } |
|
Grace also supports an exclude clause that excludes inherited attributes from the composite object. Similar mechanisms are used in Smalltalk traits. Eiffel has a deep rename construct, which is similar but different, since it also renames recursive requests in the body of the renamed method.