http://www.perlmonks.org?node_id=11105504

nysus has asked for the wisdom of the Perl Monks concerning the following question:

Let's say I have the following parent/child relationships:

package One; package Two; use parent 'One'; package Three; use parent 'Two';

But then let's say I write a fourth package that I sometimes, but not always, want to insert between the second and third packages:

package One; package Two; use parent 'One'; package Four; use package 'Two'; package Three; use parent 'Four';

So I need some kind of good system to tell package Three when I want it to use package Two sometimes and package Four other times. And it can get a lot more complicated than this. I might have a total of 10 different packages. One day I might need to chain packages 1->3->5->6 and another day I might need to chain packages 2->7->5->3->8.

I'm thinking I'll need some kind of configuration file to dictate which packages get used and in what order. But how would I go about dynamically changing the the use parent command so that it is variable? Can I do a BEGIN block which will pull from the configuration file and then set a variable which can be used in the the use parent directive?

$PM = "Perl Monk's";
$MCF = "Most Clueless Friar Abbot Bishop Pontiff Deacon Curate Priest Vicar";
$nysus = $PM . ' ' . $MCF;
Click here if you love Perl Monks

Replies are listed 'Best First'.
Re: Creating dynamic parent/child relationships
by Corion (Patriarch) on Sep 03, 2019 at 09:17 UTC

    Don't use inheritance for this.

    Consider having an array of "modificators" or "plugins" (for lack of detail) that the main class calls:

    my $foo = One->new_from_config('myconfig.yml'); print Dumper $foo->frobnicate($bar); sub One::frobnicate( $self, $item ) { for my $step (@{ $self->steps }) { $item = $step->frobnicate( $item ); }; return $item }

    The One::frobnicate method dispatches the call to all the listed ->frobnicate methods in ->steps. The ->steps array is filled with classes (or instances) from the configuration.

    See also Module::Pluggable, which can load plugins.

      Ok, I'm having trouble wrapping my head around your example. And I'm not sure if things are complicated by my particular situation because I rely on SUPER calls. Here's a simplified representation of the classes that have the relationships hard coded:

      package Base ; sub new { my $class = shift; bless {}, $class; } sub do { my $s = shift; print "hi Base here\n"; } package One ; use parent 'Base'; sub do { my $s = shift; $s->SUPER::do; print "hi from pkg one\n"; } package Two ; use parent 'One'; sub do { my $s = shift; $s->SUPER::do; print "hi from pkg two\n"; } package Three ; use parent 'Two'; sub do { my $s = shift; $s->SUPER::do; print "hi from pkg Three\n"; }

      And then in a script:

      my $three = Three->new; $three->do;
      This, of course, outputs:
      hi Base here hi from pkg one hi from pkg two hi from pkg Three

      $PM = "Perl Monk's";
      $MCF = "Most Clueless Friar Abbot Bishop Pontiff Deacon Curate Priest Vicar";
      $nysus = $PM . ' ' . $MCF;
      Click here if you love Perl Monks

      OK, I think I see what you are saying. So essentially, my Base class does the following when the "do" method is called on it:

      package Base; use One; use Two; use Three; sub do { print "hi from Base\n"; Three::do; Two::do; One::do; }

      And my config file would determine which order to run the subroutines from each of the packages. Is that right?

      UPDATE: Here is a version of the code above that is a bit more dynamic:

      package Base ; use One; use Two; use Three; sub new { my $class = shift; bless { steps => [ qw(Three Two One) ] }, $class; } sub do { my $s = shift; print "hi from Base\n"; for my $step ( @{ $s->{steps} } ) { $step->do; } }

      So I think I got it from here. I just have to dynamically import the correct modules and populate the steps attribute with the config file during construction. Nice trick. Thanks!

      $PM = "Perl Monk's";
      $MCF = "Most Clueless Friar Abbot Bishop Pontiff Deacon Curate Priest Vicar";
      $nysus = $PM . ' ' . $MCF;
      Click here if you love Perl Monks

        Just a quick follow up to show a crude way of getting this done by passing the order of the classes into the base class:

        package Base ; sub new { my $class = shift; for my $mod (@_) { eval "require $mod"; } bless { steps => \@_ }, $class; } sub do { my $s = shift; print "hi from Base\n"; for my $step ( @{ $s->{steps} } ) { $step->do; } }

        Then call construct new from a script:

        my $base = Base->new( qw ( One Three Two ) ); $base->do;

        $PM = "Perl Monk's";
        $MCF = "Most Clueless Friar Abbot Bishop Pontiff Deacon Curate Priest Vicar";
        $nysus = $PM . ' ' . $MCF;
        Click here if you love Perl Monks

Re: Creating dynamic parent/child relationships
by hippo (Bishop) on Sep 03, 2019 at 09:16 UTC
    So I need some kind of good system to tell package Three when I want it to use package Two sometimes and package Four other times.

    Sounds to me like you want to use roles. See eg. Role::Tiny.

      I'm looking into this possibility now. I've used roles with Moose and so I'm familiar with them. In my particular case, the plugins I want to use contain identical method names. So will that make the use of Roles impossible?

      $PM = "Perl Monk's";
      $MCF = "Most Clueless Friar Abbot Bishop Pontiff Deacon Curate Priest Vicar";
      $nysus = $PM . ' ' . $MCF;
      Click here if you love Perl Monks

        Why would it? SSCCE:

        use strict; use warnings; package Foo; use Moo; package Trapezist; use Role::Tiny; sub swing { print "Whoooosh!\n"; } package Trumpeter; use Role::Tiny; sub swing { print "1, 2, ... 1, 2, 3, 4, Hit it!\n"; } package main; my $person1 = Foo->new; Role::Tiny->apply_roles_to_object ($person1, 'Trapezist'); my $person2 = Foo->new; Role::Tiny->apply_roles_to_object ($person2, 'Trumpeter'); $person1->swing; $person2->swing;

        Maybe the solution is to precede the subs with some kind of unique identifier for each of the packages that the calling package can look up.

        $PM = "Perl Monk's";
        $MCF = "Most Clueless Friar Abbot Bishop Pontiff Deacon Curate Priest Vicar";
        $nysus = $PM . ' ' . $MCF;
        Click here if you love Perl Monks

Re: Creating dynamic parent/child relationships
by haj (Vicar) on Sep 03, 2019 at 10:31 UTC

    I don't think I understand how you want to insert a fourth package "sometimes, but not always" in the hierarchy of package Three. Is this for different programs using the same package Three, for different runs of the same program, for different modules in one program which use package Three - or for different objects within the same program? Does the decision happen when you write your code, or at compile time, or at run time?

    If the decision happens at runtime on a per-object base: I once had such a problem and found Moose with its roles to be a suitable approach (because the program used introspection anyway). Thanks to its meta protocol, Moose allows to add roles (which would be your intermediate packages) to existing objects, so objects can "learn new tricks during runtime".

    If the decision happens once for a program run, I'd go for a plugin mechanism as suggested by Corion in Re: Creating dynamic parent/child relationships.

Re: Creating dynamic parent/child relationships
by LanX (Saint) on Sep 03, 2019 at 11:43 UTC
    I consider this a not very wise approach. Others already explained why.

    Anyway if you are looking for a footgun ... @package::ISA is a global variable, you are free to dynamically mess with it.

    Beware of multiple inheritance though.

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

Re: Creating dynamic parent/child relationships
by talexb (Chancellor) on Sep 03, 2019 at 17:12 UTC

    May not be appropriate, but you can use the if pragma to conditionally load a module.

    use if $some_condition, Three;
    (That's page 1019 in the Fourth Edition of The Camel, in case you have a dead tree version, which I do.)

    Alex / talexb / Toronto

    Thanks PJ. We owe you so much. Groklaw -- RIP -- 2003 to 2013.

Re: Creating dynamic parent/child relationships
by jcb (Parson) on Sep 04, 2019 at 03:17 UTC

    This is a point where you really need mixins if you want to stick with object inheritance, but maybe you should rethink whether "file sorter" IS-A File::Collector or whether File::Collector HAS-A "file sorter" (or many file sorters as the case may be).

    The idea of subclassing File::Collector to add categories seemed neat at the beginning, but this is starting to get unmaintainable.

    To do this with mixins, define a "final derived class" that inherits from File::Collector and any number of "mixin" classes that provide sorting categories, using a form of the _run_chain method you mentioned in Unable to turn off strict refs to invoke methods across the mixins instead of working up a SUPER:: chain as the earlier versions did. Your application can adjust the @...::ISA array in that derived class (which should be otherwise empty) according to configuration, as LanX mentioned. Rewriting inheritance chains at runtime, while possible in Perl, is getting very close to "thermonuclear footgun" on the maintainability scale, and is almost certainly best avoided.

      Yeah, I'm feeling a little lost in the weeds. Taking away inheritance looks like it will necessitate implementing a lot of hacks and ugliness in the code to get to work properly. I was hoping to make it dead simple to implement a plug-in. My module works well, as is, with the parent-child inheritances hard coded in to the classes. I'll keep toying with it and see what I can come up with. At a minimum, I'll at least learn a lot.

      $PM = "Perl Monk's";
      $MCF = "Most Clueless Friar Abbot Bishop Pontiff Deacon Curate Priest Vicar";
      $nysus = $PM . ' ' . $MCF;
      Click here if you love Perl Monks

      Well, thanks to Hippo and his suggestion to use Role::Tiny above, the module now has a way to flexibly change the order of the Collectors and Processors. Onward!

      $PM = "Perl Monk's";
      $MCF = "Most Clueless Friar Abbot Bishop Pontiff Deacon Curate Priest Vicar";
      $nysus = $PM . ' ' . $MCF;
      Click here if you love Perl Monks

Re: Creating dynamic parent/child relationships
by Anonymous Monk on Sep 04, 2019 at 16:30 UTC
    This definitely smells like an "XY Problem" that has probably already been solved – several good metaphors have already been suggested here. And the key decision factor is: "sometimes, but not always." Once the scenario has been more carefully defined, an existing stock solution should readily suggest itself, and from this an appropriate implementation in Perl.

      Why do you waste everybody's time?