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

Has anyone else had a desire to reverse inheritance? I don't just mean adding your module to some other module's @ISA - in perl, that's simple enough. I don't even entirely mean OO modules, either. It's more of a "replacement" of a module, such that when you think you're calling Foo->bar(), you actually get Baz->bar() instead. No source filters, either.

I have two such examples floating in my head... and want to expose them to some air, to see if this is simple enough to do and/or there is a simpler way to approach it.

The first example is a Getopt::Long wrapper. For me, this is a global object, so I don't really care about allowing a bunch of objects dealing with it, especially not different objects. Regular objects would be able to use derivation normally anyway.

In this case, what I would want is that any time someone calls Getopt::Wrapped->getOpt(), as an example, they really get MyOpts->getOpt() instead, if MyOpts is set up to replace Getopt::Wrapped. This isn't that big of a deal, except in a larger framework where I check parameters all over the place, all via Getopt::Wrapped->getOpt('foo'), even though the current script has overridden some things.

What I've kind of done here is create a $singleton object, allowing that when MyOpts->new is called, the object is blessed into MyOpts, and then stored for later use. But that only works because in my case, I never want to reuse this code for arbitrary @ARGV handling where I may have more than one derivation of Getopt::Long going at the same time in the same process.

I'd prefer that MyOpts.pm would look kind of like this:

package MyOpts; use Module::Replace 'Getopt::Wrapped', qw(new);

Then, when I call Getopt::Wrapped->new, I'd get MyOpts->new instead. In my theory, this "Module::Replace" would do the same thing as "use base", but would also substitute the &new in Getopt::Wrapped with the one in MyOpts. The tough part here would be how to call SUPER:: on a method, as I'm pretty sure that would break here. I think my workaround here would be that Module::Replace would rename &new to SUPER_new, which would work fine inside MyOpts - it's a module written based on Module::Replace, so slight changes in API would be ok.

If I want more than one derivation - one for global (using Module::Replace), and others for other parsing, the others simply would not replace. That would mostly work, depending on what the global derivation overrode.

The next example is far more convoluted, and is somewhere that I think something like Module::Replace is more useful, though I haven't done anything with this yet. Think of a set of objects that can create other objects in a many-to-many relationship. Now think that each of this objects are not of the base type, but derived. However, it's the base package that is doing the many-to-many instantiations. The base packages don't know what the derived packages are, but need to create them. I have two basic thoughts on this:

a) create a factory package. Mandate that the user of the base packages update the factory module to create the correct objects. Instead of trying to call Type->new(@args), I call Factory::create('Type_Name',@args), because I don't know what Type is (the user could be calling it My::Type). This just doesn't feel perlish - such a dynamic language shouldn't be constrained by this method. Mandating a factory also seems rather harsh for a language that doesn't even want to impose "private" methods on users ;-)

b) Mandate that the user of the base packages puts "use Module::Replace Base::Type => qw(new);" at the top of their package. Now in the base framework, I can just call Base::Type->new(@args) to get the right object created. Except for one minor detail: the new method can't just bless into whatever package specified. It would have to use __PACKAGE__. Of course, if there is any concept of it being further inherited from, then it would have to merely change: $class = __PACKAGE__ if $class eq 'Base::Type';

Any thoughts?

Replies are listed 'Best First'.
Re: Reversing (some of) inheritance
by jdporter (Paladin) on Apr 22, 2008 at 21:09 UTC
    You say:
    I don't even entirely mean OO modules, either. It's more of a "replacement" of a module

    If we take that as a "requirement", then any approach whose description includes the words "bless" or "new" is right out.

    I think, other than reaching under the hood with something in the B family, the most straightforward way would be to replace subs in package Foo with the subs of the same name in package Bar:

    package Bar; sub override_Foo { *Foo::new = \&Bar::new; *Foo::method2 = \&Bar::method2; # etc. } 1;
    You can trick Exporter into doing this for you:
    package Bar; use Exporter; @Bar::ISA=qw(Exporter); @Bar::EXPORT=qw(new method2); sub override_Foo { package Foo; # how dare we! Bar->import; } 1; # and in main: use Foo; use Bar; Bar->override_Foo();
    A word spoken in Mind will reach its own level, in the objective world, by its own weight
      I don't even entirely mean OO modules, either. It's more of a "replacement" of a module
      If we take that as a "requirement", then any approach whose description includes the words "bless" or "new" is right out.

      True - I suppose the idea in non-OO modules would be that you could interject the "correct" code in the right place(s), but that wouldn't necessarily mean you want to replace everything. Which is why I had the "qw(new)" on the use lines above: so that you could say exactly what you wanted to override.

      The purpose of this is really to allow me to try and simplify the use of a generic framework, so the only thing that really needs overriding in my case is new, but even then, nothing in perl says you have to use 'new' as your constructor. But, in the same way I'm contemplating taking an existing framework and splitting it into generic and specific pieces so that other departments can use the generic parts, I'm trying to think of how to create this functionality generically.

      For example, I have some code running on Windows that needs to create a unix-style path (the output is eventually read and used on a unix box). Obviously, File::Spec->catfile doesn't work. Unless I replaced it. In my case, I had control over the function that was creating the path, so I could just use File::Spec::UNIX->catfile on all platforms instead, so that was fine. But if it were in code that I didn't control, e.g., something from CPAN or even one of the modules that shipped with perl, then replacing File::Spec's catfile, even just temporarily, would be the only thing I can think of that would work. (Ok, I can think of others, but none really any better... doing an s:\\:/:g doesn't necessarily work if the code starts to try to run on, say, MVS.)

      So I'm not necessarily thinking that every function needs to be replaced in the "parent" module. Merely an arbitrary subset (where "subset" has the mathematical meaning: set X is a subset of itself). And now I'm also thinking that a "no" (unimport) method should be there, too, to undo it.

      Also, I have problems with duplicate information, and related information in separate locations. My problem, then, with the factory module is that adding or changing subclasses means changing a separate, otherwise unrelated, file: the factory module. Being able to "use Module::Replace Base::Package qw(...)" means that it's colocated with the module that would otherwise be created via the factory. Rename the module? No problem. Add a new module? If this is part of the standard template used for modules, your work is already done.

      Heck, even in factory setup, I'd be tempted to initialise the factory method in BEGIN blocks in the modules that the factory would initialise. And that would be ugly, too.

        So I'm not necessarily thinking that every function needs to be replaced in the "parent" module. Merely an arbitrary subset

        Cool; that's what my approach above allows.

        So, I tested the idea using Exporter, and it works just fine. (There is one glitch, though: you get "redefined" warnings which cannot be silenced merely by doing no warnings just before the import call. Apparently Exporter turns on such warnings explicitly. Perhaps someone who is more expert on Exporter can explain how to silence the warnings.)
        Extending it to let the caller define the set of methods to override:

        . . . sub override_Foo { my $pkg = shift; local @Bar::EXPORT = @_; package Foo; # how dare we! Bar->import; } # and then: Bar->override_Foo(qw( new method2 ));
Re: Reversing (some of) inheritance
by ursus (Acolyte) on Apr 22, 2008 at 21:56 UTC

    How about this?

    Package GetoptReplacer; sub foo { } sub bar { } our $baz; # magic! my %g = %Getopt::; %Getopt:: = %GetoptReplacer::; our $AUTOLOAD; sub AUTOLOAD { goto &$g{$AUTOLOAD}; } # less magic 1;

    I don't know if this will catch fire, but there it is.