Alan G wrote:
Steven D'Aprano <steve <at> pearwood.info> writes:

fact that multiple inheritance itself is often the wrong thing to use, and even when it is right, it is often tricky to get it right. To put it another way: don't use multiple inheritance unless you have to, there are better ways, such as by composition.

Or use a language where MI is the normal and idiomatic way to do things because the language assumes it and so it just works. There are very few such languages but Lisp is one :-)

I'm sorry, but I don't believe you :/ If it "just works" in Lisp, why does Lisp allow you to override the default linearization?

I point you again at the series of articles by Michele Simionato in my previous post. The problems he points out with MI are mostly general to MI itself, and not to any one specific language implementation. Here's another one where he discusses implementing mixins without inheritance, and traits:

http://www.artima.com/weblogs/viewpost.jsp?thread=246488

One problem with MI (another is that it encourages huge hierarchies that are impractical to use) is when you have diamond diagrams. This is unavoidable in languages where all objects have a common superclass, such as Python (new style classes only) and Lisp.

http://en.wikipedia.org/wiki/Diamond_problem

As you can see from the multiple designs chosen by different languages, there is no one "right" way to resolve the problem, but it's generally agreed that the least-worst (or most desirable, if you prefer) resolution is the so-called C3 linearization pioneered in Dylan and implemented by Python.

The problem with Lisp's default linearization is that it is not monotomic. See this paper for details:
http://192.220.96.201/dylan/linearization-oopsla96.html

Converted to Python, if you have this class hierarchy:

class Boat(object): pass

class Dayboat(Boat): pass

class Wheelboat(Boat): pass

class Engineless(Dayboat): pass

class SmallMultihull(Dayboat): pass

class PedalWheelboat(Engineless, Wheelboat): pass

class SmallCatamaran(SmallMultihull): pass

class Pedalo(PedalWheelboat, SmallCatamaran): pass


both Dylan and Python generate the optimal linearization:

Pedalo, PedalWheelboat, Engineless, SmallCatamaran, SmallMultihull, Dayboat, Wheelboat, Boat, object

while CLOS gives (or at least gave, at the time the paper was written) the surprising result that Wheelboat is promoted ahead of Dayboat:

Pedalo, PedalWheelboat, Engineless, Wheelboat, SmallCatamaran, SmallMultihull, Dayboat, Boat, object

Why is this surprising? Because Dayboat is more specific in the linearization of *both* of Pedalo's direct parents, and yet it is less specific in Pedalo itself. This is a bad thing.


Both Lisp and Python/Dylan agree that there are potential class diagrams that are inconsistent. Here's an example from Python:

>>> class A(object):
...     pass
...
>>> class B(object):
...     pass
...
>>> class C(A, B):
...     pass
...
>>> class D(B, A):
...     pass
...
>>> class E(C, D):
...     pass
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Error when calling the metaclass bases
    Cannot create a consistent method resolution
order (MRO) for bases B, A


This gives you a clue as to part of the problem with MI: in general, multiple inheritance is inconsistent. It's only usable at all by imposing certain restrictions on it. Clearly there are no such impossible or inconsistent class diagrams if you limit yourself to single inheritance. MI is inherently more complicated.


Sadly Python isn't, and when using MI I always avoid super()

Diamonds were rare in old-style classes in Python, but when they occurred, Python's MI was potentially buggy. That is, there were combinations of class order that lead to real bugs (although other combinations did not). Unfortunately, *all* new-style classes form a diamond in MI.

This is the problem that linearization solves for new-style classes, but only if you use super(). If you call the methods explicitly, you're doing it wrong, and it's only by chance (or by hard work, or by duplicating the logic of Python's MRO!) that your class hierarchy is not buggy.

One problem with MI is that automatic method resolution does not play well with manual method resolution, and so mixing classes that use super() with classes that don't is a recipe for trouble. This is not a problem with MI, or with super, but with the classes that don't use super(). So if you're avoiding super(), you're classes are unsafe for others to inherit from using MI.

In practice, unless you control the class and can take responsibility for it, or if it is documented as being MI friendly, you should never inherit from it except using single inheritance.



--
Steven

_______________________________________________
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
http://mail.python.org/mailman/listinfo/tutor

Reply via email to