Inheritance and Aliasing Methods

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:

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:

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.

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.

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.

Traits

Grace 0.7 supports traits — reusable collections of methods that can be “mixed in” to class declarations. Classes (and object constructors) can easily use more than one trait. For example, an orderingFromCompare trait could define a suite of ordering methods based on a compare method:

As you can see, a trait declaration looks pretty much like a class declaration, except that it uses the keyword trait rather than class. You can’t see the restrictions on traits: unlike a class, a trait may not contain var or def declarations, nor inline code. Classes can use multiple traits, but can inherit from at most a single superclass.

*          *          *          *          *

It is a truth universally acknowledged that any single inheritance language must want multiple inheritance

Bertrand “Jane Austen” Meyer

We found three reasons to include traits in Grace. First, most other languages to which students may move (including Java, Python, Ruby, and of course Scala, C++ and Eiffel) now support some form of trait or multiple inheritance, and even second year students studying design patterns (like Adapter or Iterator) can benefit from multiple inheritance. Second, we found traits helpful in designing our own libraries—the collections library in particular. Third, we found we needed more flexible forms of reuse when designing dialects. Grace’s dialects form a lattice, not a tree, so we needed some way of composing dialects from reusable components. All of these reasons are more important to library and dialect authors than to beginners: we don’t expect novice students to be taught about traits early on! Grace’s dialect mechanism can be used to remove traits from the language used for beginning students.

*          *          *          *          *

Advanced programmers may note that trait declarations can be though of as methods returning objects, in just the same way that class declarations are methods returning classes.  

The key difference between classes and traits is that classes may declare variables and constants and contain inline code, while traits may not. Another difference is that objects created by classes are automatically furnished with a few default method, including asString and ==, whereas traits are not. Like classes, traits may have type parameters, parameters, and may have multi-part names (although stylistically we try to avoid them).

Any trait declaration can be turned into a class by changing the “trait” keyword to “class”.

Classes

Classes in Grace 0.7 are simpler than they used to be. A class declaration creates a “factory method”, that is, a method that returns a new object. Writing:

declares a factory method that returns an object with readable fields
colour and name, a variable that counts the number of mice the cat has
eaten, and two methods to eat a mouse and to miaow. Other than
Grace’s multi-part method names, this syntax is pretty much the same,
say, as Python or Scala, and with pretty much the same semantics.

To create objects from  the class, we just request the method directly:

Note that we don’t need parens around the arguments, because they are already delimited by quotes.  If the arguments had been expressions, then we would need parens.

More advanced programmer might want to know that classes aren’t primitive.  The cat()colour() class declaration above is pretty much equivalent to the following method declaration:


The key difference between this design and earlier and the earliest designs are that in the earlier designs, class declarations made objects that had a single factory method.  The main reason for the change is that the new design is simpler.  Most Grace programs did not use the actual class object; they simply requested the factory method. Programmers can always explicitly make an object if they need one, by putting the class declaration inside an object declaration.  In particular, classes at the top-level of a module are already inside an object: the module object.  There is often no need for another one.

One situation in which an enclosing object is useful is when there are several alternative ways to construct instances.  For example, a graphics library might provide methods r()g()b() and h()s()l() for constructing new colors, as well as a small selection of constants yellow, purple, brown, etc. It makes sense to make all of these methods on a colour object; r()g()b() might be a class, while h()s()l() and the named colors might make requests on r()g()b() with appropriately calculated arguments. Like this:

The old “dotted class” syntax did not support such a use, because the automatically declared object could have only a single factory method.

Ever Onward!

EVER ONWARD — EVER ONWARD!
That’s the spirit that has brought us fame!
We’re big, but bigger we will be
We can’t fail for all can see
That to serve humanity has been our aim!
Our products now are known, in every zone,
Our reputation sparkles like a gem!
We’ve fought our way through — and new
Fields we’re sure to conquer too
For the EVER ONWARD I.B.M.

Keeping up the blog has not been one of our strong points, but we have been making some progress in the last few months. We’ve been working our way through a review of Grace’s design and specification, with a few nontrivial changes:

  • Classes create methods, rather than objects, so no longer have “dots” in their name
  • Calling methods in superclasses relies on aliasing, not “super requests”
  • Traits support stateless reusable components
  • A simpler inheritance model that supports classes inheriting from multiple traits
  • Replacing variadic methods with primitive sequences

We expect to describe these changes in a few blog posts soon, as we are working through a thoroughly revised specification.  We plan to release the specification and implementations around the end of February,  for feedback from the community. We then plan to hold workshops (as we did in 2011) to go over what we hope will get getting close to a 1.0 version of the language design. So: Onward! Ever Onward!