Wednesday, November 08, 2006

One reason multiple inheritance sucks

So I'm reviewing a book, and it happens to cover the way method lookups are performed in a particular language's object model. I'm not going to say what the book is because it's not relevant, and nothing discussed here has anything to do with that publication. But reading it made me realise just why even when the language allows it, my internal pain aversion system will always skip multiple inheritance as an option. Consider this Python code (with built-in python shell promptness):




>>> class One(object):
... def doStuff(self):
... print "One.doStuff()"
...
>>> class Two(One):
... pass
...
>>> class Three(One):
... def doStuff(self,x):
... print "Three.doStuff(%d)" % x
...
>>> class Four(Two,Three):
... pass
...
>>> class Five(Three,Two):
... pass
...
>>> a=Four()
>>> b=Five()


so, what is the method signature of a.doStuff() and b.doStuff()? Well, it depends entirely on the order in which the class hierarchy gets inspected, Python does things by leftright-bottomtop so both a and b inherit Three.doStuff(self,x). In the case of a, Four doesn't have doStuff() so it looks at Two, which doesn't so it looks at Three and finds it. However, Python also has another lookup mechanism (which was the only one available pre-2.3 or possibly 2.4, and can still be triggered by omitting the "object" superclass from One) which is left-bottomtop-right, so a inherits One's doStuff() and b inherits Three's. In the words of Peter Cook, that could confuse a stupid person...



So luckily Objective-C avoids this problem by only having an inheritance tree, and we can't add method implementations via protocols either. And my specific example is moot, because -doStuff and -doStuff: are different selectors.

2 comments:

Anonymous said...

The only languages in which I've found multiple inheritance to really make sense are the Lisp-family languages like Common Lisp and Dylan. There, classes are arranged in a heterarchy, but classes only represent nouns (state); all behavior is implemented via function application rather than message sends.

Multiple inheritance makes sense in these languages because multiple methods can be defined for the same generic function name whose arguments vary on class/type, and the method whose (left-to-right) argument types are most specific to the actual passed arguments will be chosen at runtime. This is very logical and straightforward, though it can of course introduce its own subtleties...

(No doubt leeg knows most or all of the above. But thanks to Google, I feel compelled to be explicit these days.)

Graham Lee said...

I was thinking about that with respect to Objective-C: in my mind an equivalence would be if a class could inherit two methods such as:
-(id)doStuffWithObject:(id)anObject
-(id)doStuffWithObject:(NSNumber *)anObject
[not that anyone would write that, but there you go]. The GNU runtime could support those as different selectors but I don't think it would be able to resolve them properly, besides which the compiler would balk anyway. The NeXT/Apple runtime as it currently stands can't distinguish these selectors.

Not that that really matters, because we've already got forwarding which let us do the same things that multiple inheritance does, but in what is IMO a much less surprising way. You *know* that a method will only go down the forwarding route if it wasn't resolved in _the_ class hierarchy...unless someone does something scary with factory methods but that would generally be considered hidden internal behaviour anyway (and you're still calling _the_ factory method in any case).