Inheritance

In the beginning, single inheritance is an implementation hack for having a tiny bit of cheap genericity when managing sequential structures: the gross idea is that a function that only depends on the first n fields of a structure will also work on any structure that has the same kind of first n fields, independently from what further fields the structure has. Inheritance has then been theorized as an operation by which object interfaces are derived from others, by enriching them with new entries. Multiple Inheritance generalizes it, by allowing to define a new interface starting from the union of a set of many previous interfaces, instead of only one previous interface. Oddities that require special care happen in case of conflicts between old interfaces, or between old interfaces and new definitions, etc.

Inheritance is the typical example of an element of tradition that people mistakenly consider as being holy, and refuse to reject even though reason demonstrates how inadequate it is for all the purposes it was proposed for. It used to be a nifty hack to achieve a few high-level things at reasonable cost in low-level languages, and was since presented as a universal panacea for program design, despite its being a conceptual mess, and a brake to a really clear understanding of programming.

Inheritance is proposed as a way to provide incremental definition of objects. It grossly fails at doing so, as shows the need to carefully design and maintain inheritance trees/graphs, that can't be incrementally modified or enriched, but through addition of leaf nodes, least havoc be wroken. Those trees and graphs, i.e. partial orders, are presented as the ultimate structure for object design, relying on the myth that "order" (the fact that things be well-defined) be the same as "order" (a reflexive antisymmetric transitive relation).

Inheritance was also explored as a way to implement subtyping, as confirmed by the standard interpretation that a class B inherits from class A iff every instance of B is an instance of A (people often say that inheritance is to model the "is a" relation). This works just fine in so-called "dynamically typed" languages, such as CLOS, Smalltalk, Objective-C, or Self, where you can equate the type of an object with its immediate (non-recursive) interface.

However, this just doesn't work in "statically typed" languages, as soon as the type system includes recursive types. In such languages, such as C++, Java, Eiffel, inheritance fails to implement subtyping, as originally intended and often incorrectly alleged. Then lots of people go at length trying to develop very complex and contorted theoretical and practical tools to integrate inheritance into larger designs, whereas it would have been a trifle with subtyping.

If we formalize inheritance in statically-typed languages, we see that we must distinguish the concept of a class from the type of direct instances of a class. A class is then a function taking a type and returning a type. A subclass B of a class A is then a function that for any type t, returns a type B(t) that is a subtype of A(t). Only the type of direct instances of a class A is the fixpoint of A, i.e. a type t such that t=A(t), and the fixpoint of a subclass B is usually not a subtype of it (except very particular cases, for example, when there is no recursion in the definition of A). (An example illustrating this point would be helpful.)

From this formalization, inheritance appears as a very special-case useful construct, whereby functions from type to type (classes) can be defined, but, they can only be either instantiated (fix point), or specialized through a complex processes of refining; the refining of a class must be linear (does not handle duplication or in-depth transformation of arguments), with only one argument in the case of single-inheritance, and bizarre constraints in the case of multiple-inheritance; that refining can't be abstracted or factored, every refinement pattern can be applied only once, and it must be right after having been defined.

If such a special-case construct is any useful then imagine how much more useful could be the generic constructs unleashed! Instead of a limited and contorted construct like inheritance, we could have a clean type-system as a whole algebra of types, defined from a small set of orthogonal operations, among which explicit abstraction over types and type-refinements, higher-order functors, fix points, merging, delegation, renaming, etc. With such a type-system, one could still do inheritance as a special case, but no one would, since inheritance is such a minatory concept.

For people who want to do better than inheritance in languages with recursive types, such type systems already exist (see OCAML).

As for incremental programming, the only right approach to it is Reflection. This allows modifications which pervade the system of objects in complicated ways to be made in, to some degree, an automated manner.

For example, one may wish to do something as simple as the widespread renaming of an entity throughout the system, or something as complicated as tracing the possible paths of data of a particular type through objects, and automatically inserting security checks on that data at the appropriate places. Non-reflective incremental programming models do not support this flexibility of refinement.


This page is linked from: Io   Object-Oriented