Stateless Multiple Inheritance with Typed Selectors
Last updated at 1:58 pm UTC on 16 January 2006
by Anthony Hannan
Better factoring and reuse of code is achieved through multiple inheritance. Ambiguity is avoided by having typed selectors (selector namespaces) and no instance variables (primitive accessors only). Only true method conflicts can exist, which raise an error if not overridden locally. The 'super' pseudo variable is removed in favor of renaming (aliasing) overridden methods. This prevents methods from being bound to the class hierarchy and thus conforms to the flattening property, as explained by Traits. In fact, this multiple inheritance is equivalent to Traits if you removed Traits' extra layer of classes and single inheritance, allowed primitive accessor methods in traits, and typed selectors.
Selectors are bound to their interface (typed), thus binding messages to the role/interface they are calling. This is preferred to typed variables, which require type casting to assume different roles/interfaces. Typed selectors avoid ambiguity in multiple inheritance, and provide better information for type inference. The interface associated with a selector is simply the behavior that introduced the selector. For example, #do: would belong to the Collection interface, even though other subclasses may override it. Two unrelated behaviors may introduce the same selector name, but they are still two different selectors. Messages may have to distinguish which selector they mean by prefixing it with its interface name, such as #collection.do:, however, with global namespaces (pools) overloaded selector names should not be seen too often. Since messages are bound to interfaces, a behavior can only be polymorphic with another behavior if they inherit from the same interface. In other words, a behavior can only implement methods for its selectors and its inherited selectors. This structure yields better factored code, but does not hinder the programmer since selectors can be moved out into a new interface easily, with the original behavior and the new behavior inheriting from it.
Instance variables are replaced with primitive accessor methods, allowing multiple inheritance without the "diamond problem", and greater flexibility in reuse. Since the state slot index can change for each subclass, these primitives only work on concrete instances of its method class, so they have to be overridden or copied in subclasses (which can be done automatically).
Class variables are replaced by primitive methods as well, except these primitives just hold and return an object. Furthermore, an override of this type of method may be specified as an incremental change (for adding to collections). Many incremental changes from different inherited behaviors can be merged together without conflict.