Constructors for Collections

Grace 0.7 introduces Lineups – a built-in constructor for immutable iterables. So [1, 2, 3] is an iterable with three elements.  This will enable us to remove variable-arity methods.  For example, the constructors for Set, List, etc in the collections library will now take a single iterable rather than a variable number of arguments.

On the inside, methods that take an iterable as an argument are not very different from those with a variable number of parameters.  The method declaration is different, because the parameter is declared as xs:Iterable<Number> instead of *xs:Number, but we think that this is clearer. In both cases, the type of xs is such that the body of the method can iterate over it and deal with one Number at a time.

From the outside (the requestor’s view), the main change is that variable-arity method requests like

will be replaced by single argument requests like

You can use iterable constructors directly, for example, to initialise variables or constants

but, because we intentionally provide Iterables with a limited protocol, it’s more typical to use them to create collection objects:

*          *          *          *          *

The old design using variable-arity methods initially seemed more “neutral”, because it did not require honouring one particular kind of collections with the privileged of being built in, while others were relegated to a library.  However, if the truth be told, both designs rely on a built-in implementation of some sort of collection.  The old design created these collections implicitly whenever a request was received by a variable arity method; the new design creates the Iterable explicitly before the request is made.  Escape analysis should permit both designs to be optimised either in a JIT or by a whole-program compiler, should that become an issue.  But the new design makes it much simpler to check that methods are requested with the correct number of arguments.

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.