This is prompted by something Reg Braithwaite pondered about Java, What does Barbara Liskov have to say about Equality in Java? It's also drawn from a conversation I was having with psykotic on Reddit.
Java is unique as an object oriented language because of its object model and this has important consequences for polymorphism.
Actually polymorphism is one of my favourite subjects when discussing object orientation because its so important and so poorly understood. Even though I am going to talk about Java let's get started with Smalltalk.
bird waddle. bird quack.
If bird
understands the commands waddle
and quack
then we're justified in calling it a duck, i.e. that it is an instance of some class called Duck
. This is what most people know as duck typing and it's a common idea in all sorts of object oriented languages (and actually in all sorts of other types of programming languages too).
So far so good? Actually so far so bad. My explanation is absolutely and totally and one hundred percent wrong.
Duck typing isn't about whether the object is a Duck
or not, it's about whether the object waddle
s and quack
s or not.
This form of polymorphism is called operational polymorphism. Personally I find this term makes it clearer what it is that we're really talking about, but actually they both describe the same thing.
We don't actually care what sort of duck bird
is. We don't even really care if bird
is a goose so long as it does a convincing enough impression of being a duck.
This is what the Liskov Substitution Principle puts into mathematical terms for us¹ [1Although for some reason the paper itself confuses the issue by talking about inheritance.]. The paper describes exactly how convincing that goose needs to be for us to be able to use it as a duck in a variety of different circumstances.
We can see the same principle at work in Javascript:
function pond( bird ) { bird.waddle(); bird.quack(); }
The function pond
will work with any bird
s that we pass it so long as they waddle
and quack
. I'm labouring the point here, but that's because it's important.
For both the Javascript and the Smalltalk versions what happens if the bird
s they're given don't waddle
and quack
? We get a runtime error is what happens.
In C++ it looks a little different, but the effect is exactly the same:
template< typename B > void pond( B &bird ) { bird.waddle(); bird.quack(); }
Again we can use pond
with anything that waddle
s and quack
s, but we've now swapped our runtime error for a compile time one. The reason is that in C++ this sort of duck typing happens in the compiler when the software is built, not at run-time when the software is executed. This limits our use of operational polymorphism in C++ and there are many times we can't use it where we would be able to in a dynamic language.
When James Gosling was designing Java he didn't want to have templates because they complicate the syntax too much. Fair enough, but object oriented idioms are based on operational polymorphism so he had a problem.
The solution comes from thinking a bit more about my fist explanation.
If
bird
understands the commandswaddle
andquack
then we're justified in calling it a duck, i.e. that it is an instance of some class calledDuck
.
I said that this was completely wrong, but maybe there is a little something to it anyway? What if we can structure² [2If I understood psykotic properly then operational polymorphism can be considered as a form of structural sub-typing.] some sort of type from the operations we need? What would a solution like that look like?³ [3Actually it might not look like this because it's so long ago since I wrote any Java I've probably got the syntax wrong.]
interface Duck { pulbic void waddle(); public void quack(); }
What this has done is to translate the operational polymorphism into inclusional polymorphism. Inclusional polymorphism is where we talk about a given type as including the sub-classes of the class⁴ [4Note that there is a subtle difference in the meaning of the terms “type” and “class” here. The distinction is important, but it's not one I feel I understand well enough to try to explain.If I understand psykotic then this is a form of nominal sub-typing.].
This is the secret of Java interfaces and why they're needed. It's almost impossible to do all sorts of interesting object oriented style things if you don't have operational polymorphism and this meant that he had to add interfaces that could be added to any class.
Of course James had also wanted to leave multiple inheritance out of Java because he felt it was too hard to use properly. This is true, but it's not necessarily much harder than normal, single inheritance. Of course normal inheritance is actually much harder to use properly than it looks, but that's a story for another time. One thing it does do though is to complicate both the compiler and the syntax because of something called the "Diamond problem".
In any case he couldn't have it both ways. In order to have operational polymorphism he had to use either templates or multiple inheritance. In the end he went with multiple inheritance but in a slightly restricted form.
As well as the one or two things I've already linked these are worth reading:
Duck
interface can be done.