Truth is stranger than fiction, but it is because Fiction is obliged to stick to possibilities; Truth isn’t.
Quite similar is the story of programming and programming languages. For the past many years now the landscape of programming languages has been the following:
Fiction (or what people predicted would be the next generation): Program Verification, Static typing and type checking in “enterprise” level code and Statically typed Object Oriented language design.
Truth (or what has come to be the reality now): Runtime type safety, No static type checking in the program, dynamic and less verbose programming languages, “enterprises” which move quickly to these languages seem to gain huge productivity from programmers.
The advent of scripting languages into the mainstream has proven beyond doubt the world of programmers would favor “no types” than “better types”. In most cases, type systems generally come in the way of programming than assisting it — especially in environments geared towards rapid prototyping.
I kind of feel the same about OO programming too. Especially the inheritance aspect of it. This is beyond static type checking. I feel constrained when I am asked to extend the functionality of a class by just overriding its methods. Sometimes I would really like to change the behavior of a method very slightly… not exactly accomplished by overriding (and mostly rewriting it). In some cases it can be achieved by dividing the original method into smaller parts and overriding one of them… but by then the interfaces are laid out and the damage has already been done. In other cases, even this approach fails.
Many OO advocates pride themselves in carving out the correct set of interfaces such that the problem above really never occurs in practice. While I surely don’t doubt their skills, I still think that a very large fraction of programmers (may be close to all) have come across instances when even after carefully laying down interfaces, future evolution of the software didn’t exactly follow.
So here’s a somewhat radical idea. What if we forget inheritance the way we know it? Lets assume that we are operating in a dynamic OO language. The inheritance is based on the concept of a patch (just a regular diff between similar lines of code). To derive from a parent class you copy the source, change it according to your needs, rename the class adding a dependence declaration pointing to the parent and take a diff of the body of the derived class with respect to its parent as its source code. Upon compilation, the compiler uses the parent’s source code to dynamically generate the derived class’ source code and then compiles it.
This approach does come with advantages which I will argue are beyond traditional inheritance. The derived class now has the potential of changing the behavior of the parent in any way it feels. The process of derivation from another class is now truly “dynamic” (in the sense of — left to the programmer, the language and the compiler get out of the way). This advantage IMHO is a huge win. It is not necessarily more fragile than normal class inheritance. In both cases changing the parent class in some way may or may not conflict with the derived class. The compilation itself becomes simpler because implementing this is almost as simple as applying patches. The patch application test serves as a sanity check similar to the checks with inheritance (non conflicting member variables, etc).
Another advantage of this approach is that we have now really made inheritance and polymorphism orthogonal to each other. Previously, the derived class included the parent class’ signature in it. It contained all its methods and member variables and the public ones would be accessible. Thus, even though inheritance could allow you to re-use a class and completely change its behavior, the fact that the parent’s methods are still accessible made that logically inconsistent. Think of a Queue class which supports enqueue and dequeue at both the head and the tail. Its perfectly fine to derive a Stack class from it which just has push/pop. Its implementation involves using the Queue’s enqueue and dequeue at either the head or the tail for the push/pop operations but very importantly, also changing its interface to push/pop! This is not possible with traditional inheritance but is possible with patch based inheritance.
I hope I have atleast convinced you that prevalent OO programming is not exactly a good way to program.