Beefy Boxes and Bandwidth Generously Provided by pair Networks
Think about Loose Coupling
 
PerlMonks  

•Re: Solving the SUPER problem in Mixins with a Dispatch Method

by merlyn (Sage)
on Oct 14, 2004 at 07:16 UTC ( [id://399121]=note: print w/replies, xml ) Need Help??


in reply to Solving the SUPER problem in Mixins with a Dispatch Method

Once I came to terms with the fact that the SUPER:: dispatch system is somewhat arbitrary
It's not the case that the "SUPER" is "arbitrary". It's very specific. The problem is when you choose to use multiple inheritance. Most OO hackers say that multiple inheritance is generally evil. You should consider redesigning your system so that you are using composition and delegation instead of MI, and life would be better for you and the people who have to maintain your code.

-- Randal L. Schwartz, Perl hacker
Be sure to read my standard disclaimer if this is a reply.

  • Comment on •Re: Solving the SUPER problem in Mixins with a Dispatch Method

Replies are listed 'Best First'.
Re^2: Solving the SUPER problem in Mixins with a Dispatch Method
by fergal (Chaplain) on Oct 14, 2004 at 12:33 UTC

    NB: Nothing in this is helpful for the solution of the problem, it's just some OO stuff I've been thinking about.

    I'd say the problem is a lack of specificity, not just when you call SUPER but in all methods calls. What does $self->shoot do? If $self is a soccer player then we can expect a ball to be kicked at the goal but if $self is a gunslinger then we can expect an injury. Problems arise when $self is a person who knows how to play football and shoot a gun. In this case, what is the meaning of $self->shoot? This sort of problem is why mixins are viewed as a bad thing but I think this can be solved the same way we've solved other name clashes.

    We have namespaces for so many things but not for methods. If we had namespaces for methods then we could write $self->SoccerPlayer::shoot. Of course we wouldn't actually want to write that because it's a pain, we'd want to write

    treat $self as SoccerPlayer; # do lots of soccer playery things with $self $self->shoot;
    and if our language is smart enough we won't even need to write the "treat as" most of the time, it will be obvious because this is a method for the SoccerPlayer mixin.

    Amazingly enough, depite being a huge bag of suckiness Borland's Object Pascal (the laguage inside Delphi) incorporates something vaguely like this, except without any smartness or help from the compiler. In it's interface dispatch setup you can tell it that method soccer_shoot implements shoot when we're being treated as soccerplayer and method gunslinger_shoot implements shoot when we're being treated as a gunslinger. Of course the designers then went and ruined it by not allowing mixins. They provide a way to disambiguate methods based on context but then limit your ways of sharing methods among classes.

    Delegation and composition lead to lots of boilerplate delegation methods (which you can autogenerate if you're lucky) and it leads to situations where the subobject method calls have to be passed in a reference to their parent object so that they can manipulate it. For example a gunslinger may have a reload() method which looks in the persons various pockets for bullets and loads the gun so the delegation method in Person looks like

    sub reload { $self->gunslingerimplementer->reload($self) }
    and the other reload method looks like
    sub reload($parent) { my $bullets = $parent->pockets->getbullets(6); $parent->gun->load($bullets); }
    Then there's the question of where does the gun live. If it lives inside the gunslinger object then it makes it hard for the parent to manipulate it. If it lives inside the person object then it's an extra bit of stuff I need to remember when I'm adding gunslingability to my person. With mixins, we can set it up so that we get a gun field along with the relevant methods.

    So it's possible to have mixins without the usual problems and I think it would work very nicely (SUPER would go up to correct chain of classes), except that no language that I know of implements namespaced-methods.

      Seems to me that method namespaces would be overkill for a problem that could be solved in one of several other ways.

      First, 'shoot' is a badly chosen method name for either the GunSlinger or SoccerFootballPlayer Mixin (hehe). The same problem would occur in real life (sic) if the gun-toting centre forward happened to be dribbling the ball into the enemies 18 yard box when the opposing goalie went for his deringer. Someone shouts "Shoot!". What does he do?

      Yes, it's a fairly unrealistic scenario, but then so is the program that requires the Person class to utilise both Gunslinger and SoccerPlayer mixins concurrently.

      It's like the 'diamond inheritance' problem described elsewhere, it not too hard to dream up simplistic examples of such problems, but I wonder how often they actually occur in real code?

      I am not a fan of MI either, but the problems I've encountered with it almost all stem from the complexity of designing Classes and Class hierarchies with MI. And the main one of those problems is that MI prevents you from getting on with the job at hand.

      Rather than being able to concentrate on designing a Class to do a particular function, you are forever scrabbling around the existing class hierarchy trying to find the best place to slot it in; decide how to best leverage existing code; attempting to re-use every vaguely similar piece of code in the library; and trying to construct the class in such a way that the next time someone wants to do something similar, they will be able to inherit as much common functionality from this class as possible.

      The upshot of this process is, that gazillions of hooks, stubs and extra layers are added in just incase. And experience shows that they rarely get used.

      MI just puts the design process into quagmire of terminal inter-dependancies.

      Even the most thoroughly well thought through Class hierarchies needs wholesale refactoring the moment someone comes up with a new requirement.

      The namespace clash problem with Mixins can most simply be solved by using less generic (more specific) naming conventions for method names. $person->shootBall; and $person->shootGun;, but rather than worrying incessantly about such problems at the design stage, it's probably expedient to wait until such conflicts actually arise.

      One possible solution to the potential problem, would be to have syntax available to allow Mixin method names to be aliased by calling(?) classes. Eg. Something like:

      my $person = new Person() is Footballer( shoot as shootBall ), is GunSlinger( shoot as shootGun ) ;

      I find it hard to imagine the scenario where a gun-toting footballer would need to generically dispatch to the 'shoot' method according to which roles he was playing at the time. It's much more likely that any code needing to call either the shootBall or shootGun methods is going to be explicitly coded.

      Whether this would hold true for (all) real code examples is an open question.


      Examine what is said, not who speaks.
      "Efficiency is intelligent laziness." -David Dunham
      "Think for yourself!" - Abigail
      "Memory, processor, disk in that order on the hardware side. Algorithm, algorithm, algorithm on the code side." - tachyon

        The problem with choosing the right names is that it requires some sort of psychic ability to foresee the clashes that might happen. It also leads to long method names even when there is no danger of ambiguity. For example inside the method Footballer::TakePenalty we shouldn't have to say shootBall because no other form of shooting makes sense in this context.

        Namespaces are an inherently more powerful and general way of disambiguating and allow solutions that are simply not possible when you just make longer and longer names instead.

        That is why we have packages and functions like Data::Dumper::Dumper() not data_dumper_dumper()

        Without namespaces, calling a method is like asking for an action where the implementation and the entire meaning of the action is left to the object - so basically you are just hoping that you and the object both have the same vocabulary. With namespaces, only the implementation is left up to the object, the meaning of the method call is open to interpretation.

        So for example, assuming that all the format() methods have been implemented correctly, not using namespaces means that you could end up with a formatted disk when you really wanted a formatted string. If namespaces are available then there can never be any such nasty accidents. A more real example of the dangers of allowing the object to supply not just the implementation but the meaning.

        use IO::All; my $file = io("somefile"); log(5, $file, "some error"); sub log { my ($file, $level, $message) = @_; $file->print($message) if $level < $DEBUG_LEVEL; }
        Getting the arguments swapped causes us to silently record rubbish into our log because IO::All changes not just the implementation but the entire meaning of <.

        I think the reason this hasn't been implemented yet it because we get quite a bit of disambiguation by looking at the object on which the method is called. However sometimes that's not enough - mixins being a prime example.

        Aliasing is a horrible hack and breaks genericity - if the method no longer has the correct name then you cannot pass that object into a generic function and expect it work.

        As for unreal examples, yes, they're easy to create but real examples to exist. Where I really missed MI recently was when implementing a DB layer in Object Pascal. I needed to implement objects which were essentially cursors and allowed you access to the fields of the current row. I had to implement 3 types, one for ADO, one talking directly to ODBC and another talking to SQLite. So I inherited various bits from a base class and had 3 specialised subclasses, one for each target. The problem is that there are various behaviours of cursors that are orthogonal. They can be forward-only or random access cursors, updateable or read only. Mixins would have allowed me to bolt on these behaviours to the existing classes. I had to use delegation for the updateable vs readonly and I ended up having to write lots of boilerplate delegation methods. For the forward only situation I ended up putting the shared code into the base class and so there was no distinction at a class level between forward only and random access. This meant that I had to wait until run time to catch people trying to go backwards with a forward only cursor. If I had made them separate classes, these errors would have been caught at compile time but using delegation to do that was too fiddly to be worth it.

        Delegation causes code like this

        package Thingy; sub method { my $self = shift; $self->del->method($self, @_); } pakage Delegate; sub method { my $self = shift; my $parent = shift; # in here we completely ignore $self and do # lots of manipulation of $parent }
        which is just plain wrong. Even if you can autogenerate the first method the second method should make you exceedngly worried because it completely ignores $self and spends all it's time poking around inside $parent. It is crying out to be a method in Thingy rather than in Delegate.
        The upshot of this process is, that gazillions of hooks, stubs and extra layers are added in just incase. And experience shows that they rarely get used.

        I don't think this is anything to do with delegation or mixins, this is simply trying to stuff too much into a single class and can happen by either method.

        Can you point out anything specific that is dangerous or unworkable when using mixins along with namespaced-methods?

        We are getting mixins in Perl 6 (via something like traits). I think the apocalypse, the exegesis (if it's out) and that paper will demonstrate their usefulness. Unfortunately I think we are getting at some of their bad points too.

Re^2: Solving the SUPER problem in Mixins with a Dispatch Method
by rir (Vicar) on Oct 14, 2004 at 12:29 UTC
    ... multiple inheritance is generally evil. You should consider redesigning your system so that you are using composition and delegation instead of MI, and life would be better

    I believe you. But could you point out any texts that actually make that case.

    Thanks.

    Be well.

Re^2: Solving the SUPER problem in Mixins with a Dispatch Method
by simonm (Vicar) on Oct 14, 2004 at 16:43 UTC
    Most OO hackers say that multiple inheritance is generally evil. You should consider redesigning your system so that you are using composition and delegation instead of MI, and life would be better for you and the people who have to maintain your code.

    As I tried to explain in my earlier node, I agree with the central thrust of this position, but think of it as "generally" true rather than a hard-and-fast rule. (One could say that AUTOLOAD and string evals were "generally" evil, or at least "sometimes" evil, but of course that's just one layer of the onion, and everyone recognizes that there are cases in which those techniques are perfectly valid.)

    I explicitly did consider using composition and delegation instead of multiple inheritance, but decided that in this case mixin classes would be a more straightforward mechanism. A base class and collection of mixin classes is hardly a wild and wooly case of MI: there's explicit sequencing of classes, no diamond networks, and a well defined base class.

    In the specific case of Text::MicroMason, one of my goals was to keep the base class simple. (If it grows significantly I might have to take "Micro" out of the package name.) I certainly understand why HTML::Mason has separate classes for each activity (resolver, lexer, assembler, compiler, etc), but given the deliberately small scope of the MicroMason distribution, I selected this other technique, in which activity is a method which can be overridden by subclasses.

    So, yes, I know that in the general case MI is "considered harmful", but is there something about this particular implementation that strikes you as more evil than the average? Of course, if you have comments about specific ways in which my life could be better, I'm open to hearing them...

Re^2: Solving the SUPER problem in Mixins with a Dispatch Method
by SpanishInquisition (Pilgrim) on Oct 14, 2004 at 13:14 UTC
    Good post -- This is basically what I was failing to say so eloquently in the previous thread with my MetaThing example ... inheritance is the proverbial over-used hammer of Software design.

    As for texts that say that (someone wanted examples), well, this is mainly an opinion that one need to form by oneself, or you need to use languages that don't use inheritance as an uber-hammer. Java zealots use inheritance as an uber-hammer all the time, so the "texts" as you say are probably sending mixed messages.

    Compare and constrast (ack! sounds like high school), for instance, Ruby File I/O with Java File I/O, and which one makes you want to strangle a small furry animal. Keeping it simple, and knowing when to encapsulate is a good thing. I am actually able to (by reasonable use of encapsulation and a good object model), forego the need for mixins most of the time as well. KISS (Rock and Roll every night, party every day) design usually doesn't need it. The trick is encapsulating other classes and asking them to do things for you, rather than trying to have your one class "be" everything.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://399121]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others musing on the Monastery: (2)
As of 2024-04-25 12:47 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found