Friday, 8 June 2012

Polymorphism and Inheritance are Independent of Each Other


Flexible programs focus on polymorphism and not inheritance.  Some languages focus on static type checking (C++, Java, C#) which links the concepts and reduces polymorphic opportunities.  Languages that separate the concepts can allow you to focus on polymorphism and create more robust code.  JavaScript, Python, Ruby, and VB.NET do not have typed variables and defer type checking to runtime.  Is the value of static type checking worth giving up the power of pure polymorphism at runtime?

Inheritance and polymorphism are independent but related entities – it is possible to have one without the other.  If we use a language that requires variables to have a specific type (C++, C#, Java) then we might believe that these concepts are linked.

If you only use languages that do not require variables to be declared with a specific type, i.e. var in JavaScript, def in Python, def in Ruby, dim in VB.NET then you probably have no idea what I'm squawking about! J

I believe that the benefits of pure polymorphism outweigh the value of static type checking.  Now that we have fast processors, sophisticated debuggers, and runtime exception constructs the value of type checking at compile time is minimal.   Some struggle with polymorphism, so let's define it:

Polymorphism is the ability to send a message to an object without knowing what its type is.   

Polymorphism is the reason why we can drive each others cars and why we can use different light switches.  A car is polymorphic because you can send commonly understood messages to ANY car (start(), accelerate(), turnLeft(), turnRight(), etc) without knowing WHO built the car.  A light switch is polymorphic because you can send the message turnOn() and turnOff() to any light switch without knowing who manufactured it.

Polymorphism is literally what makes our economy work.  It allows us to build functionally equivalent products that can have radically different implementations.  This is the basis for price and quality differences in products, i.e. toasters, blenders, etc.

Polymorphism through Inheritance


The UML diagram above shows how polymorphism is stated in languages like C++, Java, and C#.  The method (a.k.a operation) start() is declared to be abstract (in UML), which defers the  implementation of the method to the subclasses in your target language.  The method for start() is declared in class Car and specifies only the method signature and not an implementation (technically polymorphism requires that no code exists for method start() in class Car).

The code for method start() is then implemented separately in the VolkswagenBeetle and SportsCar subclasses.  Polymorphism implies that start() is implemented using different attributes in the subclasses, otherwise the start() method could simply been implemented in the super class Car.

Even though most of us no longer code in C++, it is instructive to see why a strong link between inheritance and polymorphism kills flexibility.

// C++ polymorphism through inheritance

class Car {
// declare signature as pure virtual function
     public virtual boolean start() = 0; 
}

class VolkswagenBeetle : Car {
     public boolean start() {
          // implementation code
}
}

class SportsCar : Car {
     public boolean start() {
          // implementation code
}
}

// Invocation of polymorphism
Car cars[] = { new VolkswagenBeetle(), new SportsCar() };

for( I = 0; I < 2; i++)
     Cars[i].start();


The cars array is of type Car and can only hold objects that derive from Car (VolkswagenBeetle and SportsCar) and polymorphism works as expected.   However, suppose I had the following additional class in my C++ program:

// C++ lack of polymorphism with no inheritance


class Jalopy {
     public boolean start() {
         
}
}

// Jalopy does not inherit from Car, the following is illegal

Car cars[] = { new VolkswagenBeetle(),new Jalopy() };

for( I = 0; I < 2; i++)
     Cars[i].start();

At compile time this will generate an error because the Jalopy type is not derived from Car.  Even though they both implement the start() method with an identical signature, the compiler will stop me because there is a static type error.

Strong type checking imposed at compile time means that all polymorphism has to come through inheritance.  This leads to problems with deep inheritance hierarchies and multiple inheritance where there are all kinds of problems with unexpected side effects.  Even moderately complex programs become very hard to understand and maintain in C++.

Historical note: C++ was dominant until the mid 1990s simply because it was an object oriented solution that was NOT interpreted.  This meant that on the slow CPUs of the time it had decent performance.  We used C++ because we could not get comparable performance with any of the interpreted object-oriented languages of the time, i.e. Smalltalk.

Weakening the Link

The negative effects of the tight link between inheritance and polymorphism lead both Java and C# to introduce the concept of interface to pry apart the ideas of inheritance and polymorphism but keep strong type checking at compile time. 

First it is possible to implement the above C++ example using inheritance as shown by the C# below:

// C# polymorphism using inheritance

class Car {
     public virtual boolean start();  // declare signature
}

class VolkswagenBeetle : Car {
     public override boolean start() {
          // implementation code
}
}

class SportsCar : Car {
     public override boolean start() {
          // implementation code
}
}

// Invocation of polymorphism
Car cars[] = { new VolkswagenBeetle(), new SportsCar() };

for( I = 0; I < 2; i++)
     Cars[i].start();

In addition, through the use of the interface concept we can write the classes in Java as follows:

// Java polymorphism using interface

interface Car {
     public boolean start(); 
}

class VolkswagenBeetle implements Car {
     public boolean start() {
          // implementation code
}
}

class SportsCar implements Car {
     public boolean start() {
          // implementation code
}
}

By using an interface, the implementations of the VolkswagenBeetle and SportsCar can be completely independent as long as they continue to satisfy the Car interface.  In this manner, we can now get our Jalopy class to be polymorphic with the other two classes simply by:

class Jalopy implements Car {
}


Polymorphism without Inheritance

There are languages where you have polymorphism without using inheritance.  Some examples are JavaScript, Python, Ruby, VB.NET, and Small Talk. 

In each of these languages it is possible to write car.start() without knowing anything about the object car and its method. 

# Python polymorphism

class VolkswagenBeetle(Car):
     def start(): # Code to start Volkswagen

class SportsCar(Car):
     def start(): # Code to start SportsCar

# Invocation of polymorphism
cars = [ VolkswagenBeetle(), SportsCar() ]
for car in cars:
     car.start()

The ability to get pure polymorphism stems from these languages only have a single variable type prior to runtime i.e. var in JavaScript, def in Python, def in Ruby, dim in VB.NET.   With only one variable type there can not be type error prior to runtime.

Historical note: It was only during the time frame when Java and C# were introduced that CPU power was sufficient for interpreted languages to give sufficient performance at run time.  The transition from having polymorphism and inheritance tightly coupled to being more loosely coupled depended on run time interpreters being able to execute practical applications with decent performance.

There is no Such Thing as a Free Lunch

When type checking is deferred to runtime you can end up with strange behaviors when you make method calls to objects that don’t implement the method, i.e. sending start() to an object with no start() method.

When type checking is deferred to runtime you want an object to respond “I have no idea how to start()
if you send the start() method to it by accident.

Some pure polymorphic languages generally have a way of detecting missing methods:
  • In Visual Basic you can get the NotImplementedException
  • In Ruby you either implement the method_missing()  method or catch a NoMethodError exception
  • In Smalltalk you get the #doesNotUnderstand exception

Some languages don’t have exceptions but there are clunky work arounds:
  • In Python you have to use the getattr() call to see if an attribute exists for a name and then use callable() to figure out if it can be called.  For the car example above it would look like:
startCar = getattr(obj, "start", None)
if callable(startCar):
    startCar ()
  • JavaScript (ECMAScript) will only raise an exception for a missing method in Firefox/Spidermonkey.

Even if you have a well defined exception mechanism (i.e. try catch), when you defer type checking to runtime it becomes harder to prove that your programs work correctly. 

Untyped variables at development time allow a developer to create collections of heterogeneous objects (i.e. sets, bags, vectors, maps, arrays). 

When you iterate over these heterogeneous collections there is always the possibility that a method will be called on an object that is not implemented.  Even if you get an exception when this happens, it can take a long time for subtle problems to be found.

Conclusion

The concepts of polymorphism and inheritance are linked only if your language requires static type checking  (C++, Java, C#, etc).  Any language with only a generic type for variable declaration has full separation of polymorphism and inheritance (JavaScript, Python, Ruby, VB.NET), whether they are compiled to byte code or are directly interpreted.

The original compiled languages (C++, etc) performed static type checking because of performance issues.  Performing type checking at compile time created a strong link between inheritance and polymorphism.  Needing deep class inheritance structures and multiple inheritance lead to runtime side effects and code that was hard to understand.

Languages like C# and Java used the notion of an interface to preserve type checking at compile time to weaken the link between inheritance and polymorphism.  In addition, these languages compile to a byte code that is interpreted at runtime to give a balance been static type checking and runtime performance.

Languages like Ruby, Python, JavaScript, Visual Basic, and Smalltalk take advantage of powerful CPUs to use interpreters to defer type checking to run time (whether the source code is compiled to byte code or purely interpreted).  By deferring type checking we break the link between inheritance and polymorphism, however, this power comes with the difficulty of proving that a subtle runtime problem won't emerge.

The one caveat to pure polymorphism is that we may develop subtle bugs that can be difficult to track down and fix.  Pure polymorphism is only worth seeking if the language that you are using can reliably throw an exception when a method is not implemented.


Effective programmers are seeking polymorphism and not inheritance.  The benefits of pure polymorphism outweigh any advantage that compile time type checking provides, especially when we have access to very sophisticated debuggers and support for runtime exception handling. In general, I believe that the benefits of pure polymorphism outweigh the value of static type checking.

2 comments:

  1. actually Python do have Exceptions and you can catch a missing method like this:

    try:
    MyClass.missing_method()
    excpet AttributeError:
    #do smething smart

    ReplyDelete
  2. I'm sorry, maybe I was not clear before...
    the "missing_method" is just a method that does not exist.
    If you try to call that method and it is not specified you get an AttributeError exception that you can eventually catch.
    just as NotImplementedException in VBasic, NoMethodError in Ruby, etc...

    ReplyDelete