** 21. Multiple Inheritance, Multiple Subtyping, and Types vs. Classes ** ---------------------------------------------------------------------------- 21.1. Multiple inheritance allows one class to inherit its implementation from several other classes. If the operations and instance variables of the superclasses are disjoint (from other superclasses), then MI is straightforward. However, if there is overlap, then there becomes a question of what to do. If there are multiple instance variables named x, then there is the choice between storing one merged x instance variable or several separate x instance variables. Similarly, if there are multiple methods named foo, then it is unclear which one to inherit, or whether to report a problem. In general, the declared multiple inheritance edges form a partial order. Some choices: *) Linearization: turn the partial order into a total order, then use regular single inheritance rules. Also, merge duplicate instance variable declarations. How to linearize? Ah, that's the question. There are several different published linearization algorithms, but all are based on the order superclasses were listed in each class's inheritance declaration (superclasses listed earlier come sooner in the total order than superclasses listed later). Advantages: always resolves lookups "successfully", allows "super" messages to visit every superclass in the heirarchy. Disadvantages: hard for programmers to predict behavior, particularly with non-local interactions across classes in some linearization algorithms. Common Lisp does this. *) Partial ordering: use the partial order directly, with methods in subclasses overriding methods in superclasses, and methods in unordered sibling classes considered mutually ambiguous. Advantages: pretty simple, pretty expressive. Disadvantages: rejects some messages as ambiguous, requiring explicit programmer intervention (or is this an advantage?), no easy way to visit all superclasses in a hierarchy. Cecil does this. *) Tree-ization: break sharing of superclasses by replicating them, turing a DAG into a tree. Then report ambiguities. Advantages: simple implementation. Disadvantages: no automatic ambiguity resolution at all (or is this a feature?). C++ non-virtual inheritance does this. C++ virtual inheritance retains sharing, in somewhat complicated ways. *) Mixins: parameterize subclasses by their superclass (their superclass is a parameter, not hardwired). Then can combine such "mixin" subclasses separately, essentially doing linearization explicitly in how mixins are composed to form classes. Advantages: simplicity, expressiveness, explicitness. Disadvantages: difficult to typecheck and compile mixins w/o knowing the superclass. C++ templates can do this, although most programmers don't. *) No MI: disallow all MI as too complicated. Can keep multiple subtyping, since there are no clashes of implementation (duplicate interfaces don't hurt). Advantages: simple. Disadvantages: less expressive. Java does this, as does Smalltalk (no MI, and no types either). ---------------------------------------------------------------------------- 21.2. Most OO languages don't distinguish inheritance from subtyping: each class has a corresponding type, and (mostly) vice versa, and if one class inherits from another, its type is a subtype of the other's type, and (mostly) vice versa. Exceptions can be made for built-in types that aren't classes. Java deviates some from this in distinguishing interfaces (Java's form of types) from classes, but this just allows types and subtyping that doesn't have analogous classes; classes and inheritance still has analogous types and subtyping. But in theory, types and classes are distinct. One can subtype from another type without inheriting code; Java's interfaces give examples of this. One can inherit w/o subtyping; this happens if one allows subclasses to delete methods that would have been inherited, or make them private, or do method overriding that doesn't follow the contravariant subtyping rule. So, should they be distinguished in real programming languages? This is an open question. Cecil distinguishes them, but provides syntactic sugar for the common case of declaring parallel classes/types and inheritance/subtyping. I am now of the opinion that a decent language need not distinguish them at all, and that other language mechanisms can eliminate the need for distinguishing classes & types.