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

I'm trying to inherit and over-ride a package's functions. What I've come up with is this (for demonstration purposes):

The original package

package orig::functions; BEGIN { use Exporter (); use vars qw(@ISA @EXPORT_OK); @ISA = qw(Exporter); @EXPORT_OK = qw(func1 func2 func3); } sub func1 { print "hello"; } sub func2 { print "kind"; } sub func3 { print "world"; }

The replacement package. Inherits original functions, replaces some and adds new ones

package replace::functions; BEGIN { # import the original functions, but not the ones # this package is going to replace use orig::functions qw(!/func1|func2/); use Exporter (); use vars qw(@ISA @EXPORT_OK); @ISA = qw(Exporter); # let the original functions be exported as well # as the new ones @EXPORT_OK = (@orig::functions::EXPORT_OK, qw(funcx funcy) ); } # func1 is going to be replaced with our own sub func1 { print "goodbye"; } # func2 is going to be replaced with our own sub func2 { orig::functions::func2(); print "(haha)"; } # func3 is reused from orig::functions unchanged # a couple new functions sub funcx { print "new x"; } sub funcy { print "new y"; }

Now to pull it all together...

# program A use orig::functions qw(func1 func2 func3); print func1(), func2(), func3(), "\n";
and
# program B use replace::functions qw(func1 func2 func3 funcx); print func1(), func2(), func3(), "\n"; print funcx(), "\n";

My goal is to use orig::functions as common base of shared functions. But I'd also like to modify the shared functions as required with replace::functions. This layout works as intended it, but it feels like I'm skulking around corners to achieve it. Or maybe I'm just looking at the problem the wrong way. Is there a cleaner way to get the same results without using blessed objects?

Replies are listed 'Best First'.
Re: Sharing and over-riding package functions
by kennethk (Abbot) on Dec 11, 2008 at 17:53 UTC

    You should note that the use vars qw(@variable) is an outdated way of creating package globals - the syntax our @variable (parallel to my) is preferable, though both are functional.

    I'm not quite following your logic on how you are building your modules. Generally you only override a method in an inheritance context (perltoot) - it looks more like you should be doing selective importing into whichever program you are writing.

    use ModuleA qw(func1 func2); use ModuleB qw(func3);

    particularly since you are already using exporter. You could certainly use this approach to build suite sets if you just wanted one use statement at the head of your script.

      Selective importing is what I'm trying to do inside of replace::functions.

      When program B calls 'use replace::functions qw(func1)', I don't want program B to a have to know or care if func1 is coming from replace::functions or orig::functions.

        Right, so you think of your packages as function suites.

        package Functions::Base; use strict; use Exporter; our @ISA = qw(Exporter); our @EXPORT_OK = qw(func1 func2 func3); sub func1 { print "hello"; } sub func2 { print "kind"; } sub func3 { print "world"; } return 1;

        and

        package Functions::Alternate; use strict; use Exporter; use Functions::Base (@Functions::Base::EXPORT_OK); our @ISA = qw(Exporter); our @EXPORT_OK = (@Functions::Base::EXPORT_OK, qw (funcx funcy) ); # func1 is going to be replaced with our own no warnings 'redefine'; sub func1 { print "goodbye"; } # func2 is going to be replaced with our own sub func2 { Functions::Base::func2(); print "(haha)"; } use warnings 'redefine'; # func3 is reused from orig::functions unchanged # a couple new functions sub funcx { print "new x"; } sub funcy { print "new y"; } return 1;

        and

        package Functions; use strict; use Exporter; use Functions::Base qw(func1 func3); use Functions::Alternate qw(func2); our @ISA = qw(Exporter); our @EXPORT_OK = (func1 func2 func3); return 1;

        so for subroutines

        use strict; use Functions::Base qw(func1 func2 func3); func1; func2; func3; print "\n";

        or

        use strict; use Functions::Alternate qw(func1 func2 func3); func1; func2; func3; print "\n";

        or

        use strict; use Functions qw(func1 func2 func3); func1; func2; func3; print "\n";

        I'd also note that by printing the functions which contain print statements, you are uselessly printing the functional return values, which will be 1. Thus, you are appending 1 to the end of your outputs for each call.

Re: Sharing and over-riding package functions
by Tanktalus (Canon) on Dec 11, 2008 at 17:39 UTC

    You may be wanting something like Module::Replace? It can help hide the messy details behind a cleaner-looking façade :-) (Of course, looks can be deceiving...)

      Thanks Tanktalus, that was very helpful (see my reply to rir below). So I guess there's no built in way to get the same end result? I was hoping for some Perl magic I could use instead of messing with symbol tables by hand :(
Re: Sharing and over-riding package functions
by rir (Vicar) on Dec 11, 2008 at 21:39 UTC
    I guess you want the writer of "Repl.pm" to be the arbiter of what happens:
    package Orig; use Exporter; use vars qw( @ISA @EXPORT ); @EXPORT = qw( orig repl ); sub orig { print "&Orig::orig runs\n" } sub repl { print "&Orig::repl runs (O No!)\n" } 1;
    package Repl; use warnings; use strict; BEGIN { use Orig; use Exporter; use vars qw( @ISA @EXPORT); @ISA = qq(Exporter); for ( @Orig::EXPORT) { no strict 'refs'; *{$_} = \&{"Orig::". $_}; } @EXPORT = ( qw( repl ), @Orig::EXPORT ); } no warnings 'redefine'; sub repl { print "&Repl::repl runs $/" } 1;
    #!/usr/bin/perl # client program use warnings; use strict; use Repl; repl(); orig();
    Output:
    &Repl::repl runs &Orig::orig runs
    Be well,
    rir

      Oh, you were so close! But I see now what I did wrong.

      Bringing the function refs from Orig into Repl is only part of the solution. But that still fails if one of the original functions calls another. Say orig() calls repl(). Even though orig() reference has been copied into package Repl and gets called from Repl, the orig() code still belongs to package Orig. When it executes, it will call Orig::repl() not the new Repl::repl() we just worked so hard to replace. I discovered this problem in my original post (after I posted of course).

      The other part of the solution is to replace the references in package Orig for any replaced functions in package Repl to ensure that when they get called, they execute the replaced function, not the original.

      # note: adjusted @EXPORT_OK in place of @EXPORT package Orig; use Exporter; use vars qw( @ISA @EXPORT_OK ); @ISA = qw(Exporter); @EXPORT_OK = qw( orig repl ); sub orig { print "&Orig::orig runs\n"; repl(); # this will not call the replaced repl() without help } sub repl { print "&Orig::repl runs (O No!)\n" } 1;
      package Repl; use warnings; use strict; # don't run in BEGIN blocks or we won't know what functions # we have defined use Orig; use Exporter; use vars qw( @ISA @EXPORT_OK); @ISA = qw(Exporter); for ( @Orig::EXPORT_OK) { no strict 'refs'; # check if we redefine this function # maybe it might not hurt to check for a function ref? if (${"Repl::"}{$_}) { # yes, save a SUPER:: copy so we can call it later *{"SUPER::" . $_} = \&{"Orig::". $_}; # now replace the original function with our new one no warnings 'redefine'; *{"Orig::" . $_} = \&$_; } else { # *{$_} = \&{"Orig::". $_}; } } @EXPORT_OK = ( qw( newfunc ), @Orig::EXPORT_OK ); # no warnings 'redefine' - not required here as we didn't import it; sub repl { print "&Repl::repl runs $/"; # finish with our original SUPER::repl(); } sub newfunc { print "&Repl::newfunc runs $/"; } 1;
        SUPER is a poor choice for a namespace because Perl defines SUPER to indicate a special pseudo-class. Your use of SUPER does coexist as a regular class of the same name. Confusing.

        See perldoc perlobj.

        Be well,
        rir