in reply to Re: inheritance and object creation
in thread inheritance and object creation

If the answer to a question is "multiple inheritance", you've probably asked the wrong question.
Really? Suppose you were to make a class hierarchy of shapes. How would order: parallelogram, rhombus, rectangle and square?
Poorly.

The problem is that as soon as we add in multiple inheritance, we add tremendous conceptual complexities in how we figure out what to do when we have defined actions in multiple places in our class hierarchy that might reasonably (under different models of how things work) apply. And it isn't that there is a right or wrong way to do this. In different situations, different ways of resolving the same ambiguity make sense. Furthermore no way of handling multiple inheritance that I have seen will always be appropriate.

Furthermore look at your example. You offer 4 basic geometric types. But would you even really want to model them with an isa arrangement? I don't know about you, but when I think of modelling a square, most of the implementations that I would start with don't turn into good implementations of a parallelogram or rhombus. Unless, of course, I started knowing that I wanted parallelograms...

The Structure and Interpretation of Computer Programming has some interesting things to say on this. In Inadequacies of hierarchies they offer a more involved version of your example and dryly note, Dealing with large numbers of interrelated types while still preserving modularity in the design of large systems is very difficult, and is an area of much current research. In the footnotes they expand on this with, Developing a useful, general framework for expressing the relations among different types of entities (what philosophers call ``ontology'') seems intractably difficult. The main difference between the confusion that existed ten years ago and the confusion that exists now is that now a variety of inadequate ontological theories have been embodied in a plethora of correspondingly inadequate programming languages.

This remark seems to me to be dead on. When you decide how to handle multiple inheritance, you are settling on a particular ontological framework. To the extent that your framework does not naturally express the understanding of the programmer, you create confusion. The subtler and more complex the distinctions that you draw, the more readily you will find that slight confusion on the part of the programmer rapidly multiplies into obfuscation, errors, and rapidly escalating design complexity. Particularly when you get to comprehension issues that virtually nobody has decent mental models of.

Do you deny that people lack good mental models of multiple inheritance? Oh, we can all produce diagrams, and for simple geometrical shapes it is easy enough to say what the inheritance diagram should be. But where is the simple diagram and natural examples that explains how traversing the class hierarchy in one order differs from traversing it in another differs from using Class::MultiMethods to locate a method based on both arguments? You know those three are different, I know they are different, and we can both produce code proving that they are different. But I can't provide an understandable model of the difference beyond, "Here are the rules and here is how the rules will apply in these cases."

Therefore I'm inclined towards merlyn's view. If you can solve your problem using single inheritance, do so. The resulting design is likely to be easier to come up with and easier for others to understand than multiple inheritance. You also avoid the potential for several kinds of "gotchas". And when you learn another language, you can design the same way secure in the knowledge that single inheritance always is present, and its behaviour doesn't tend to change that much between languages so you have less relearning to do.

  • Comment on Re: Re: inheritance and object creation

Replies are listed 'Best First'.
Re: inheritance and object creation
by Abigail-II (Bishop) on Feb 24, 2004 at 10:43 UTC
    Furthermore look at your example. You offer 4 basic geometric types. But would you even really want to model them with an isa arrangement? I don't know about you, but when I think of modelling a square, most of the implementations that I would start with don't turn into good implementations of a parallelogram or rhombus. Unless, of course, I started knowing that I wanted parallelograms...
    Yes, I want to model them in an isa arrangement. I wasn't thinking about "modelling" a square. I was thinking about a property system. A square has all the properties of a rectangle. A square has all the properties of a rhombus. Both the rectangle and the rhombus have all the properties of a parallelogram. What is more natural than multiple inheritance to express these facts? See also my response to flying moose.
    Therefore I'm inclined towards merlyn's view. If you can solve your problem using single inheritance, do so.
    While I agree with the latter, I don't think that's what Merlyn said. What he said was:
    If the answer to a question is "multiple inheritance", you've probably asked the wrong question.
    which to me means "never use multiple inheritance".

    And when you learn another language, you can design the same way secure in the knowledge that single inheritance always is present, and its behaviour doesn't tend to change that much between languages so you have less relearning to do.
    That I find a weak argument. I'm not shying away from hashes because normal arrays are always present in other languages and hashes may not. Besides, there are languages that don't have support for even single inheritance (for the mere fact they don't have native OO support).

    Abigail

      Furthermore look at your example. You offer 4 basic geometric types. But would you even really want to model them with an isa arrangement? I don't know about you, but when I think of modelling a square, most of the implementations that I would start with don't turn into good implementations of a parallelogram or rhombus. Unless, of course, I started knowing that I wanted parallelograms...
      Yes, I want to model them in an isa arrangement. I wasn't thinking about "modelling" a square. I was thinking about a property system. A square has all the properties of a rectangle. A square has all the properties of a rhombus. Both the rectangle and the rhombus have all the properties of a parallelogram. What is more natural than multiple inheritance to express these facts? See also my response to flying moose.
      Once you decide to use @ISA, you limit choices on how to model things. Your implementation of a Square has to be the same as your implementation of a Parallelogram. Which is an inconvenient implementation of a Square.
      Therefore I'm inclined towards merlyn's view. If you can solve your problem using single inheritance, do so.
      While I agree with the latter, I don't think that's what Merlyn said. What he said was:
      If the answer to a question is "multiple inheritance", you've probably asked the wrong question.
      which to me means "never use multiple inheritance".
      You seem to be misinterpreting what merlyn said.

      The following type of discussion is rather common (and a variation of it happened above):
      Question: How do I set up design X?
      Answer: Use multiple inheritance.
      Better Question: How can I modify design X to solve my original problem in a simple way with single inheritance?

      What merlyn said seems to be an empirical statement that discussions like that constitute most of the cases where the answer is, "Use multiple inheritance." Therefore if that is your answer, you've probably asked the wrong question.

      Incidentally I'm unclear on how you got "never" out of "probably..wrong".

      And when you learn another language, you can design the same way secure in the knowledge that single inheritance always is present, and its behaviour doesn't tend to change that much between languages so you have less relearning to do.
      That I find a weak argument. I'm not shying away from hashes because normal arrays are always present in other languages and hashes may not. Besides, there are languages that don't have support for even single inheritance (for the mere fact they don't have native OO support).
      Good point.

      I should point out that when working in languages without native OO, it is often possible to write your own OO framework. (In fact many people do it by accident.) It is easier to roll a single-inheritance scheme than a multiple-inheritance one. It is also easier to optimize single-inheritance than multiple. So if you are caught without native OO but want OO, then again it is easier to plan to design for single-inheritance.

      But your basic point is true. It makes no sense to avoid features in language X just because language Y does not possess them.

      However for people who have to move around between languages a lot, it does make sense to try to work with concepts that stay common. So you might add a library to your C programs giving you ready access to hashes and regular expressions, and then choose to stay away from multiple inheritance in other languages because its presence and details vary so much between Perl, Java, C++ and Python. That will limit your confusion.

      If you don't need to balance multiple languages, then this issue has no bearing.

        Once you decide to use @ISA, you limit choices on how to model things. Your implementation of a Square has to be the same as your implementation of a Parallelogram. Which is an inconvenient implementation of a Square.
        Why does the implementation of a square to be the same as one of a parallologram? That's not why how one typically uses inheritance. An implementation of a base class is different than one of a super class - otherwise, there would not be a point in using inheritance. A base class and a super class typically share part of their implementation - and sometimes everything of a super class is inherited by a base class; that is, the base class doesn't redefine any method, or mask any attribute, of the super class.

        As for a parallelogram being inconvenient as an implementation of a square, I'm kind of flabbergasted. I cannot think of any property a parallelogram has, that a square hasn't.

        Abigail

Re: Re: Re: inheritance and object creation
by demerphq (Chancellor) on Feb 24, 2004 at 17:19 UTC

    The problem is that as soon as we add in multiple inheritance, we add tremendous conceptual complexities in how we figure out what to do when we have defined actions in multiple places in our class hierarchy that might reasonably (under different models of how things work) apply.

    Well, surely its not too difficult to arrange that this doesnt happen? For instance MI as a means to "mix-in" stuff always seemed to me a reasonable preactice.


    ---
    demerphq

      First they ignore you, then they laugh at you, then they fight you, then you win.
      -- Gandhi


      For instance MI as a means to "mix-in" stuff always seemed to me a reasonable preactice.
      That presumes that mixins are a reasonable practice. :-)

      See Re: very simple per-object mixins for a brief discussion about whether (and when) mixins are a good idea.

      The provisional thought that I responded to perrin with is still my opinion on the topic, and is also (I just realized) what I object to about complicating Perl's OO model with things like "traits" or "roles".

      I should probably let that idea stew for a bit and then post a root meditation later on that...

        Sure you can always solve a problem with single-inheritance, but sometimes it results in an even more complex and unmaintainable mess than the worst multiple-inheritance mess. I have been reading the work on Traits a lot lately (here is a link to most of the relevant papers). I would suggest reading the one regarding the refactoring of the Smalltalk class heirarchy. It is particularly relevant.

        But understand that I agree with you too; multiple-inheritance, mix-ins, etc. are not to be used lightly. But in certain cases, base level frameworks being one, things like multiple inheritance can be extremely valuable. Just compare the Java standard lib to the Eiffel standard lib. Java goes out of it way alot to accomplish things that Eiffel does with a single multiple inheritance scenario.

        For example, in Java, Comparable is an interface with no implementation. Not much re-use here, just a single method that can be relied upon to be there at runtime. The same named Eiffel class COMPARABLE is far more than that thanks to Eiffel's multiple inheritance and deffered classes (which is not unlike Traits/Roles in a way). You get all the standard comparison operators and a few other method on top of that (see the link for details). All you need to do is define an implementation of the "<" operator and the rest comes for free. You just can't do anything like that in a single inheritance world, even with Java's "solution" to multiple-inheritance needs (aka Interfaces).

        The Traits/smalltalk collection refactoring paper details a savings of about 10% of the number of methods needed to implement the same functionality, just by using traits. They also managed to move about 8% of methods that were "too high" in the heirarchy (a common problem in large single inheritance class heirarchies). Sure those percentages don't sound like much, but we are talking about removing 68 methods from a set of 635. And moving "down" 55 methods to their proper level in the heirarchy, 15 of which had previously be explicity disabled (they threw a "shouldNotImplement" exception). This is based off a preliminary refactoring and not an exhaustive one too, so more savings may have been possible. The author claims that the heirarchy is simpler too (which I only partially agree with) and more conceptually sound (which I totally agree with).

        Just because things like multiple-inheritance, mix-ins, traits, roles, etc. aren't useful in everyday programming, don't discount them fully. They have their place and sometimes you may find they actually reduce complexity rather than increase it. Not everything should/can be shoehorned into a single inheritance world, so its nice to have the other options available IMO.

        -stvn