in reply to Re: Understanding 'Multiple Inheritance'
in thread Understanding 'Multiple Inheritance'

If objects in Perl6 will be as the apocalypse says (and other than minor changes, I've no reason to believe it will not), objects in general, and MI more specifically will be dramatically better in perl6 than in perl5.

I've never been real tempted by MI
I've always found the need to use MI very natural - perhaps partially because in the first OO environment I programmed in, MI was used a lot, and the language supported it well. One sees MI in every day objects. You have CD-players, and you have radios, but you also have devices that are both. That doesn't mean most of your objects need MI - but it does mean that the lack of MI (either by no language support, by convention, or by being pressured in the believe that MI is bad (or difficult (as one language designer/compiler implementor once said "MI isn't difficult to use - it's difficult to implement well"))) makes it harder design your programs in a natural way, or to truely make use of inheritance (and hence, code reuse).
  • Comment on Re^2: Understanding 'Multiple Inheritance'

Replies are listed 'Best First'.
Re^3: Understanding 'Multiple Inheritance'
by BrowserUk (Patriarch) on Mar 07, 2005 at 12:59 UTC
    You have CD-players, and you have radios, but you also have devices that are both.

    One of the problems with MI arises when elements common to both (all) the parent objects collide.

    In your example, both a radio and a CD player need a volume control, but the combined device does not need two. Using MI to build a CDRadio class from a CD class and a Radio class results in a conflict for methods like Set/GetVolume().

    There are two ways to resolve this using MI.

    1. Implement the MI such that it redispatches any method that returns a 'Not processed' code. If the first (by some defintion) subclass implementing the method returns this value, the method is redispatched to the next subclass that implements it, until one of them responds saying 'Okay, I've got that one'.

      Now you have the problem that every class that will ever be subclassed has to retain state to know that it is currently selected. And every method of every class that can be inherited has to first check to see if it is currently selected and return the 'Not processed' message if it is not.

      That creates a huge burden on the designers and implementors of inheritable classes to provide that state and the checks.

      It also creates a runtime cost in performing those checks.

    2. Override the Set/GetVolume() method in the superclass and manually dispatch to the approprate baseclass depending upon a the state of a 'mode' parameter.

      You would need to do this for every common method.

      This is exactly what you would do in a composite class and throws away any benefit from MI.

    And that is only one side of the problem--the inbound flow. Using your example, both sub-devices would also share a common set of speakers. The flow from the sub-devices to the speakers is initiated, probably asynchronously, from with the subdevice itself as a result of many inbound control flows: volume controls; tone controls; balance controls; mute etc.

    In order to ensure that the speakers only receive output from the currently selected device, the MI model would require:

    • the output flow from base classes to be conditional upon the settings of all of the ancillary controls + the "We are the currently selected device" flag.
    • Or, the base classes would need to expose their output methods so that the superclass could overide them to perform the "Selected" test--which creates further problems of close coupling and wider interfaces.
    • Or the bases classes would need to provide callbacks for every output method. Again, close-coupling and overhead result.

    With the composite model, the "select device" function is entirely encapsulated within the superclass and the base classes need never consider the possibility of their not being the 'top of the heap' class, structure-wise. The super class simply dispatches to the selected sub-device depending upon the state of the "selected device" flag and everything works. The non-selected device doesn't have to consider whether it is the selected device, because if it isn't, it never gets called.

    In Perl-OO terms, you could even arrange that re-dispatch to the appropriate sub-device be (for any non-overiden methods) done through an AUTOLOAD routine that simple re-blesses the object into the currently selected object and calls it (undoing the rebless on the way back out).

    And that's the crux of the problem with MI. There is no way to know what uses a class will be put to when writing it, or what other base classes it might be called upon to coexist with, and trying to write every class such that it can co-exist with any other siblings is costly in both design and implementation time and in runtime overhead.

    By deferring those considerations to only those projects where they are needed, through composition rather than MI, you simplify the design and implementation of the bases classes and the co-existance decisions. You will then know exactly what decisions need to be made, because you will know what the 'other class(es)' are.

    In the above example, the "selected device state" only needs to be stored and known within the superclass, and not all the bases classes also.

    The single biggest (and hardest) lesson I've had to learn through experience, because it was never covered on any CS course I took--nor any I have read about since--is: "Don't do it unless you have to!".

    Deferring a difficult decision until you have the information to make the it based upon the exact circumstances in which it needs to be made, saves a huge amount of speculation about what might be true when that time arrives.

    Using composition rather than MI makes that process natural and intuative.


    Examine what is said, not who speaks.
    Silence betokens consent.
    Love the truth but pardon error.
      You knew someone was going to reply from the P6 aspect, right? Please tell me you did! :-)

      Implement the MI such that it redispatches any method that returns a 'Not processed' code. If the first (by some defintion) subclass implementing the method returns this value, the method is redispatched to the next subclass that implements it, until one of them responds saying 'Okay, I've got that one'.

      Coincedentally, that's exactly (well, almost) how Perl6 will implement it. The method dispatcher will ask all the classes involved (in some order you the programmer get to specify) who wants to handle it. Each class can be written with that in mind or they can just handle it and be selfish. But, the mechanism will exist.

      The real problem with the CD player / Radio example is a that the problem was ill-conceived. A CD-player / Radio shouldn't be viewed as both is-a a CD-player and a Radio. Those items aren't the true atomic concepts as a CD-player and a Radio are each several items. A better view would be a pipeline of objects that provide for a plugin mechanism. So, you would have

      • Output(s)
      • MusicMaker(s)
      • Control(s)
      • PowerSupply(s)

      Then, you build it like you have a bunch of Legos, which is how they're built in the real world. And, you do have mode switches for most of those categories which switches the active FOO from A to B. In other words, the actual physical item is built with composition, but the Foobars each inherit from some base Foobar for an interface and some common implementation bits.

      Being right, does not endow the right to be rude; politeness costs nothing.
      Being unknowing, is not the same as being stupid.
      Expressing a contrary opinion, whether to the individual or the group, is more often a sign of deeper thought than of cantankerous belligerence.
      Do not mistake your goals as the only goals; your opinion as the only opinion; your confidence as correctness. Saying you know better is not the same as explaining you know better.

        You knew someone was going to reply from the P6 aspect, right? Please tell me you did! :-)

        Sorry, but no I didn't know. I thought someone might though:)

        Each class can be written with that in mind or they can just handle it and be selfish.

        In the first case, all the co-inheriters (and by extension, all possible co-inheritors, which is another way of saying: "every class that ever might be inherited from.") have to consider the possibility, and handle it.

        In the second case, you have to add a mechanism to the language to allow the superclass writer to hand code the inheritance/dispatch ordering. But do you do that:

        • Once for all the classes from which you are inheriting?
        • Once per coexistant method, so that you can prefer some methods from one subclass and others from another (or several others)?
        • Dynamically as the state of the superclass changes?

          When would you do this? The only obvious place is with in an superclass override of the dynamically order submethod--but then what did the MI buy you that composition wouldn't?

        In any case, I think that roles (if they turn out to be what I think they should be) will obviate the need for the above manually controllable dispatch ordering and render MI a little used feature.

        The real problem with the CD player / Radio example is a that the problem was ill-conceived.

        However, I did know that this objection would come up :)

        The problem is that hindsight is 20:20, but you can neither buy it, nor be taught it--nor inherit it:). You can only acquire it with time, and by then it is too late.

        When the radio was invented, the CD wasn't even Sci-Fi material. And yes, I know I have stretched the analogy too far, but it does hold.

        In order for your sub-elemental design to work, you need to have the entire problem laid out up-front--or spend an inordinate amount of time speculating what the future might hold.

        Taking the analogy a little further. You've designed and implemented your componentised system. It works great. Then you get the requirement legislated upon you to support a new technology that turns sound into 'pictures' that the deaf can 'hear'.

        The problem is that 'tone' controls for this technology need to mix components of both the left and right audio channels according to a bizarre (in audio terms) function so that the tone control varies the definition of the 'picture' in a 3-dimensional way. Further, the volume control needs to vary the range of colours in which the overall 'picture' is presented in. But, because deaf people also suffer the same visual maladies that other sighted people suffer--colour-blindness; short-sightedness etc.--it also becomes necessary to be able to limit the range of colours over which the volume is spread to some sub-range of the total possible to ensure they remain within the visual accuity of the user.

        That is Sci-Fi as far as I am aware, but the point is that there is no way to know now, when that requirement will become a reality, or what constraints and requirements it will impose upon existing systems. Trying to define your componentisation now, such that it will allow for their inheritable inclusion into that (and all other) future systems is an impossible task. So don't try!

        Design you system, and it's components to deal with todays requirements and realities--and no more. That allows you to keep it as clean and light as is practible now. It may mean that a future system that incorporates it (through composition) will be more bulky and need to actively override (rather than pass through) more elements than might otherwise be the case had you been able to design with that new system in mind.

        But it also means that all the other uses of it without those special ancillary requirements did not have to bear the costs of the speculative possibilities.

        If the composite system with the special requirements proves to be too heavy, inefficient or laborious, then the subsystems it uses will need to be reworked or re-designed from scratch with the new requirements in mind, but either way, the rework will now know what those new requirements are, and will be able to make specific decisions based upon them.

        Any speculative extras added into the original design might preclude that re-working, but then again, they may not. And history favours the latter. In that case, all those speculative extras in the original design simply burdened all the uses of that design that didn't use them, and all the design and implementation that went into them was wasted. Indeed, it may well be that it was the very presence of those extras that forced the re-working.

        Designing your 70's TV with a press-out slot for a Beta-Max video tape may have seemed like a good bet at the time, but ultimately would have been a bad decision :)


        Examine what is said, not who speaks.
        Silence betokens consent.
        Love the truth but pardon error.

      As DragonChild suggests, you ought to take a more component oriented approach, assembling the final product from base components.

      I would probably have something along the lines of the following classes: CDPlayerInterface, RadioInterface, AudioDeviceInterface, CDPlayer, Radio, and RadioCDCombo. The Radio class would be a marriage of the RadioInterface and AudioDeviceInterface, the CDPlayer class would be a marriage of the CDPlayerInterface and the AudioDeviceInterface, and the RadioCDCombo class would be a marriage of CDPlayerInterface and RadioInterface and AudioDeviceInterface.

      Or something like that... I'm not sure I like the specifics of my solution, but I think the general paradigm is better.

        you ought to take ...
        Ah, the classical fallacy. Whenever someone raises an objection against use of inheritance, or multiple inheritance, someone is bound to step up and say you should have redesigned your entire inheritance tree differently.

        Well, then you have missed one of the important aspects of OO: code reuse and encapsulation. You want to be able to take an existing Radio class, and an existing CDplayer class, and combine them into a Radio/CDplayer. Regardless how the Radio class and the CDplayer class are implemented. Hopefully, they are build using smaller units, and if I'm lucky, they share some parts as well. But ultimately, for me, as the constructor of the Radio/CDplayer, it shouldn't matter how the Radio and the CDplayer are implemented - that's what's encapsulation is about. I do have to care about overlap in the API - both classes may have an on method, and they both may have a volume method. My combined object probably wants two on methods (although they can't be both called 'on'), but on volume method. I do have to care about the interface, but I should not have to care about setting the volume - the inheriting classes will know.

        If I can only do (multiple) inheritance properly if I'm in full control of the entire inheritance tree, OO becomes utterly useless to me. I want to reuse code. Reuse code that I haven't written, and shouldn't need to inspect.

      BrowserUk wrote:
      The single biggest (and hardest) lesson I've had to learn through experience, because it was never covered on any CS course I took--nor any I have read about since--is: "Don't do it unless you have to!".
      Funny, the one that I've learned is "Always be neat, even when you think you don't have to."

        Funnily enough, most every course I took at college, form was favoured over content.

        I saw people that wrote the most boring, derivitive and unoriginal work consistantly score the highest marks because they were blessed with neat handwriting and good rote recall for spelling.

        And one American girl got really upset because she would be constantly marked down by our English English teacher for "mis-spellings" like favor -v- favour, color -v- colour etc.

        Her writing were damn nearly magical to most of us very parochial Brits. We loved hearing her stories of the Near and Far East, and her recounting of everyday life in West Berlin in the 70s were so far beyond our experience it was almost like SciFi. For her writing to be marked down because she had been taught to write differently to us always seemed most unfair.

        This was all the more galling for her as she had gained her education all over the world. As the daughter of a USAF guy, she had travelled all over and been schooled in many countries in Europe and Asia. And the only country where she was penalised for her spelling, and expressions like "they borrowed me a cup of sugar", was the UK.

        "Always be neat, even when you think you don't have to."

        Reminds of a guy I was at school with. He was brilliant at woodwork. His joins were always invisible, his joints barely needed the glue, his tools always sharp, and his workbench was never covered in shavings and sawdust. He became a carpenter--and hated it.

        His first commercial job was erecting shuttering. He would carefully align the edges of the boards. Every board had the same number of nails, which were always equidistantly spaced along the edges. Every nail was driven home properly with none ever being turned over. The problem was, he couldn't earn anywhere near the same amount of money as his colleagues. He was too slow and it was piecework.

        Shuttering is the wooden frameworks they build to pour concrete into when they build office blocks and bridges. It's purpose is to scaffold and support whilst the concrete dries, after which it is just ripped off. It is a temporary structure that doesn't need to be neat or perfect. It simply needs to perform its function for the few hours or days that it takes for the concrete to be poured and set. And it needs to be fast and cheap.


        Examine what is said, not who speaks.
        Silence betokens consent.
        Love the truth but pardon error.