Beefy Boxes and Bandwidth Generously Provided by pair Networks
Just another Perl shrine
 
PerlMonks  

RFC: Class::DispatchToAll

by domm (Chaplain)
on Jul 10, 2002 at 20:12 UTC ( [id://180852]=perlmeditation: print w/replies, xml ) Need Help??

If you're interested in Method Dispatching and non-standard Inheritance, please read this RFC (and maybe even Comment it..)

You can get the tarball here

There are some similar modules on CPAN (Class::Delegation, NEXT, both by TheDamian), but mine works still does something different... What I'm especially interested in is:

  • Has this already been implemented somewhere?
  • Does anybody (besides me...) need it?
  • Is there a better way to implement it?

DESCRIPTION

Class::DispatchToAll enables you to call all instantances of a method in your inheritance tree (or labyrinth..).

The standard Perl behaviour is to call only the lefternmost instance it can fing doing a depth first traversial.

Imagine the following class structure:

              C
             /
   A    B  C::C
    \  / \ /
   A::A   D
       \ /
     My::Class
Perl will try to find a method in this mess in this order:

My::Class -> A::A -> A -> B -> D -> B -> C::C -> C

(Note that it will look twice in B because B is a parent of both A::A and D))

As soon as Perl finds the method somewhere, it will short-circuit out of it's search and invoke the method.

And that is exactly the behaviour Class::DispatchToAll changes.

If you use dispatch_to_all (provided by Class::DispatchToAll) to call your method, Perl will look in all of the aforementioned packages and run all the methods it can find. It will even collect all the return values and return them to you as an array, if you want it too.

Example

Update: An even better example can be found in this node further down this thread

Merging a hash (using the Class Hierarchy shown above)

  A::hash={foo=>'foo'};
  C::hash={bar=>'bar'};
  A::A::hash={foo=>'FOO'};
  My::Class::hash={even=>'more'};

  # assuming a method get_hash not implemented in this example
  my @v=$self->dispatch_to_all('get_hash');
  my %hash=();
  foreach (reverse @v) {
      %hash=(%hash,%$_);
  }

  # %hash now looks like:
  # {
  #  foo=>'FOO',          # from A::A, overriding A
  #  bar=>'bar',          # from C
  #  even=>'more',        # from My::Class
  # }

Please note the reverse. This enables the overriding of values "further away" from the calling class by values that are "nearer"

Please see the docs in the tarball (and the examples in test.pl) for further info or /msg me

-- #!/usr/bin/perl for(ref bless{},just'another'perl'hacker){s-:+-$"-g&&print$_.$/}

Replies are listed 'Best First'.
Re: RFC: Class::DispatchToAll
by TheDamian (Vicar) on Jul 11, 2002 at 03:09 UTC
    In the same vein, the next release of NEXT.pm will feature a new pseudo-class: EVERY. This will allow you to write:
    $obj->EVERY::foo();
    to cause all of the foo methods inherited by $obj to be called.
      Will it also be possible to collect the return values of those method calls?
      -- #!/usr/bin/perl for(ref bless{},just'another'perl'hacker){s-:+-$"-g&&print$_.$/}
        Yes. Each method will be called in the same context as the original call through EVERY::, and the collective return value will be:
        • a ref to an array of scalars (in scalar context)
        • a list of refs to arrays (in list context)
        That is, EVERY:: will aggregrate the individual return values, and return them in a form appropriate to the calling context.
•Re: RFC: Class::DispatchToAll
by merlyn (Sage) on Jul 10, 2002 at 23:11 UTC
    I'm sorry, but I've been doing OO since 1980 (with the original Smalltalk 80 image). I can't see how this would be useful in the slightest. Can you give an example of a real use of this, that is reasonably normal OO and not just a laboratory case? As in, what problem were you trying to solve when you came up with this?

    -- Randal L. Schwartz, Perl hacker

      The problem this solves is very real-world. Consider a Perl class hierarchy in which a class Child inherits from classes Mother and Father:
      package Child; use base qw(Mother Father);
      If both Mother and Father have DESTROY methods, what happens? Under normal Perl semantics, only Mother::DESTROY would be called, since it's the left-most, depth-first method encountered during the dispatch. But failing to call one (or more) of an object's inherited destructors is not correct behaviour. Hence the need to be able to call all of them:
      package Child; use base qw(Mother Father); sub DESTROY { $_[0]->EVERY::DESTROY }
        The problem this solves is very real-world. Consider a Perl class hierarchy in which a class Child inherits from classes Mother and Father:

        Uh, oh. Multiple Inheritance.

        A couple of years of doing Smalltalk (which didn't have multiple inheritance, but which let you fake interface interitance via mixins), and a couple of years of Java and C++ have lead me to believe that Multiple Inheritance is a Very Risky Thing, and that it can always be worked around by either composition or reducing to inheriting from one data-bearing class and multiple (data-less) interface classes. Doing so avoids the multiple destructor problem.

        Perhaps you've run into a situation where multiple inheritance is the right thing to do. If so, I'd like to hear about it.

        OK, that problem is solved by NEXT, and discipline. It's the responsibility when you want to extend, rather than override, to call your parent method. Particularly with DESTROY.

        So I still haven't seen a place where calling all of the methods blindly is useful or maintainable.

        -- Randal L. Schwartz, Perl hacker

      My Problem: I've got several classes, looking a little bit like this:
                           App
                            |
                        App::Basic
                       /         \
      App::Basic::Special1     App::Basic::Special2 
           |                        |
           |                        |
           |            My::App     |
           |           /       \    |
      My::App::Special1         My::App::Special2
      
      Note that My::App doesn't inherit from App. And note that there are My::OtherApp, Your::App too, looking like My::App and inheriting from App::*

      Each of this Classes contains some config values as Class Data.

      I want to be able to fetch all those config values, merge them and do something with the result.

      E.g.: Say, each class contains an array called @order and a hash called %lables.
      @order contains some fields to be displayed
      %lables contains the name of those fields as keys and a "human readable" name as values.

      @order=(field1,field2,field2,..); %lable={ field1=>"Name of Field 1", field2=>"Name of Field 2", .. );

      Each Class now defines some of those values. Some Classes append new values to @order and %lables, some overwrite data in %lables.

      There are some basic fields (used everywhere in the app) defined in App::Basic. Each App::Basic::Special1/2 adds some fields unique to this special case.

      Now, My::App changes some field lables (e.g. use another language). My::App::Special1 may add even more fields that are only needed in My::App (there might be a My::OtherApp::Special1 that inherits from App::Basic::Special1 and doesn't use this fields)

      Then I call dispatch_to_all('get_fields') which fetches all those @field arrays, massage the returned array of all return values to fit my needs, and voila: I get the array of fields used by this Class, without having to specify the "Basic" fields in all Subclasses (which would result in a lot of typing and and even bigger lot of problems should I need to change a App::Basic config value)

      This was/is my problem, and that's why I wrote Class::DispatchToAll, which seems the best way to solve this problem to me. But if you now another way, please let me know...

      -- #!/usr/bin/perl for(ref bless{},just'another'perl'hacker){s-:+-$"-g&&print$_.$/}
        Then I call dispatch_to_all('get_fields')
        This is where I lost you. If a class has a get_fields, I'd expect it to return all appropriate fields. If it has overridden a base class method of the same name, then there's a reason for that, so it should either call all the immediate base class methods and derive an appropriate response from that, or simply ignore those and riffle through it's own data to produce the result.

        Perhaps you don't get why "extending" and "overriding" are useful. You are breaking that model. At that point, don't call it OO programming any more. You are merely aggregating through hierarchies, turning "IS A" into "HAS A" relationships. This will scare every single maintenance programmer that has to look at your code. Please don't do that. All the studies show that most of corporate software money is spent on maintaining, not creating, the software. You have just spent a whole lot more money than you even imagine.

        -- Randal L. Schwartz, Perl hacker

      I just thinking about something like this just two days ago! I've had to set up a heirarchy of modules to do something very similar, but the alternate dispatch system I employed was specific to those modules and not something that you could just drop in to any other heirarchy, like this is.

      The problem can be stated like this: say you want a object to try to get_money(). Say the object is a member of the classes Executive::Enron, Citizen::American and Human::AbleBodied. So you try $KenLay->get_money();. It so happens that Executive::Enron::get_money() will return a failure. However, method dispatch stops at this failure, without trying the methods in other classes. For the sake of argument, we'll call this a bad thing. (I'm speaking highly rhetorically here, ok? For a non-dark side example, consider object $YAPC, member of classes Herd::Camels and NonProfit::OfficiallyLicensed. Herd::Camels::get_money() contains 'goto &pass_hat;' but NonProfit::OfficialyLicensed::get_money() writes grant proposals.)

      You can create wrapper methods or a dispatch table to explicitly call each fully-qualifed method in order, but that would be unusably brittle. You could use NEXT, but if the modules you're dealing with already exist, you'd have to rewrite all the modules (or at least the methods, in new subclasses) from the heirarchy that you are working with.

      Thanks to the things I learned from Damian's Advanced Objects course (another satisfied customer!), I was able to devise a solution. But it wasn't particularly easy, and can't be simply carried over to other modules. I was just wondering if this sort of thing could be done in a portable fashion when I saw this. Thanks, domm!

      -- Frag.
      --
      "It's beat time, it's hop time, it's monk time!"

        Again, multiple inheritance is a broken concept in general. This whole thing reeks of "bad design".

        Why would you be passing control from your derived method to a base method that might fail? You should be calling it directly, taking the results (or none), then deciding whether or not to call the next. Again, when you inherited from the multiple base classes, it's your responsibility to decide how to handle the multiple dispatch. And that's what NEXT is for.

        -- Randal L. Schwartz, Perl hacker

Re: RFC: Class::DispatchToAll
by djantzen (Priest) on Jul 11, 2002 at 01:31 UTC

    At first blush this seems likely to introduce more (and more subtle) errors than it's worth. It requires that all of the various method bodies be written in such a way that they not step on one another's toes, but this is exactly opposite the usual concern when people write multiple versions of the same method; namely, to override functionality present at higher levels in the hierarchy!

    The one place I can see using this is in initializer methods which are (often) meant to be called in sequence all the way up the tree. (Cf. inheritance: constructors). However NEXT already does just this.

      but this is exactly opposite the usual concern when people write multiple versions of the same method; namely, to override functionality present at higher levels in the hierarchy!

      When using this module, I do not want to override methods, but explicitly call all of them (and collect the return values). You can still use normal Perl behaviour, Class::DispatchToAll is meant as an alternative to the Perl Inheritance mechanism, not as a replacement. Only use it when you need it. And it only is used when you explictly call for it...

      However NEXT already does just this.

      As frag pointed out, you would have to alter each and every method to include $_[0]->NEXT::method() at the end. Doesn't seems lazy enough to me...

      -- #!/usr/bin/perl for(ref bless{},just'another'perl'hacker){s-:+-$"-g&&print$_.$/}
Re: RFC: Class::DispatchToAll
by John M. Dlugosz (Monsignor) on Jul 11, 2002 at 14:56 UTC
    As a C++ programmer, I note that some methods work this way, namely destructors and assignment.

    I agree with the comment that the "normal" use of supplying your own definition is to override the behavior, not co-operate with multiple versions.

    However, I think it would be useful to have an "every parent" dispatch. Each class's implementation works for that (final put-together) class. When you're putting together a new one, you are responsible for coordinating the activity of your direct parent classes; they in turn deal with their parents and members.

    Having an every-parent displatch would take the place of "super" which doesn't make much sence in a multiple-inheritence environment.

Class::DispatchToAll now on CPAN
by domm (Chaplain) on Jul 16, 2002 at 19:15 UTC
    FYI: The URL

    http://domm.zsi.at/source_code/modules/Class-DispatchToAll-0.10.tar.gz

    has entered CPAN as

    file: $CPAN/authors/id/D/DO/DOMM/Class-DispatchToAll-0.10.tar.gz
    size: 4541 bytes
    md5: b418901b9ffb11aa6ad0d14557e06f6a

    -- #!/usr/bin/perl for(ref bless{},just'another'perl'hacker){s-:+-$"-g&&print$_.$/}

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://180852]
Front-paged by dimmesdale
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others rifling through the Monastery: (4)
As of 2024-04-24 20:32 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found