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

This is in reference to a mod_perl/mason issue I've been running into, but I think I can phrase it outside those environments.....

Is there anyway to get Exporter to overwrite subroutines that have already been defined? i.e. I would like the output of test.pl below to be "module subroutine" but can't get that behavior w/o the commented out line.

---- test.pl ----

#!/usr/bin/perl use My::Module qw(testsub); ### works, but seems to defeat the purpose of Exporter # *testsub = \&My::Module::testsub; print testsub(), "\n"; sub testsub { # "local" definition of subroutine return "local subroutine"; }

---- My/Module.pm ----

package My::Module; BEGIN { use Exporter (); @ISA = qw(Exporter); @EXPORT_OK = qw(testsub); } sub testsub { # new definition for subroutine return "module subroutine"; } 1;
I've looked at the specific mod_perl related modules (Apache::StatINC, Apache::Reload, Stonehenge::Reload) but none of them seem to address this. They all seem to work fine if you use the full package (i.e. print My::Module::testsub(); works fine above) but I can't get main::testsub to see the new code w/o explicitly forcing it. Any ideas?

-Blake

Replies are listed 'Best First'.
Re (tilly) 1: Redefining Exported Subroutines (esp. in mod_perl/mason)
by tilly (Archbishop) on Jun 19, 2001 at 02:16 UTC
    I once did something along that line, but eventually decided on a different architecture so I abandoned that work. You can find a basic implementation of something close to what you want at Versioned modules.

    UPDATE
    Oops, I misread the question. Your problem is not what you think it is. It is an order of action issue. Here is the order of actions with the section commented out:

    1. Start your script.
    2. Go off and load the module.
    3. import from the module.
    4. Compile the rest of the script, including overwriting the function.
    5. Execute the script, calling the function. (Get the local version.)
    Here is the order of actions after uncommenting:
    1. Start your script.
    2. Go off and load the module.
    3. import from the module.
    4. Compile the rest of the script, including overwriting the function.
    5. Manually import the function again, overwriting the local function.
    6. Execute the script, calling the function. (Get the module version.)
    As you see the issue isn't how you get the module to overwrite the local version, it is how you get the local version to not overwrite the module!

    The answer is that you can't. But if you move the use to the *end* of the script you will get the module version. If you manually call import in your script you will likewise be able to get the module version. However you cannot prevent the code using the module from being able to overwrite what you exported. Which is really how it should be. Module authors should not be deliberately equipped with weapons to allow their bad assumptions to accidentally cause hard-to-fix grief for users of their code.

      Ahhh, very enlightening.... You are correct. Moving use My::Module qw(testsub) to the end, or calling My::Module->import(qw(testsub)) both seem to give me the behaviour I wanted.

      I'll have to recheck my example to see if it also applies to the mod_perl issues I'm having.

      I understand your caveat, but in my mod_perl development environment the exported subroutine is simply clobbering an older version of itself. I'm essentially just trying to un-cache the subroutine after it gets modified on disk.

      Thanks.
      -Blake

      Update:

      This does solve my mod_perl problem. The difference between the following two lines is the key.
      1. use My::Module qw(testsub);
      2. use My::Module; My::Module->import(qw(testsub));

      #1 imports when the module is loaded, #2 imports at runtime. thanks again.

      -Blake

Re: Redefining Exported Subroutines (esp. in mod_perl/mason)
by dimmesdale (Friar) on Jun 19, 2001 at 02:03 UTC
    Well, this you'll have to do before you call it(and you might want it in a BEGIN block if it's for the whole script):
    undef testsub(); sub testsub { eval 'whatever action you want to take' }
    If this is for a small area of the program, wrap it in enclosing blocks, i.e., {}. Also, you may wish to put a 'local' in front of sub testsub.
Re: Redefining Exported Subroutines (esp. in mod_perl/mason)
by blakem (Monsignor) on Jun 19, 2001 at 10:36 UTC
    Here is the solution I finally used (in case someone else encounters the same thing)

    Problem: Changes to modules aren't seen by mod_perl / mason / apache without an explicit 'apacheclt restart'

    Solution First see Apache::Reload. This will solve most cases, except for certain exporting issues. use My::Module qw(testsub); still seems to be using the old version of testsub().
    Rewrite the above line to read. use My::Module; My::Module->import(qw(testsub)); which in conjunction with Apache::Reload should fix the problem. All scripts/pages should now see the new subroutine.

    ---- My::Module.pm ----

    package My::Module; BEGIN { use Exporter (); use vars qw(@ISA @EXPORT @EXPORT_OK); @ISA = qw(Exporter); @EXPORT = qw(); @EXPORT_OK = qw(testsub); } use strict; use Apache::Reload; sub testsub { return "module subroutine"; } 1;
    ---- test.pl ----
    #!/usr/bin/perl use My::Module; My::Module->import(qw(testsub)); print testsub(), "\n"; sub testsub { # "local" definition of subroutine return "local subroutine"; }
    ---- mason enabled index.html file ----
    <% $txt %> <%INIT> use My::Module; My::Module->import(qw(testsub)); my $txt = testsub(); </%INIT>
    ---- stuff in httpd.conf ----
    PerlInitHandler Apache::Reload PerlSetVar ReloadAll Off
    ---- stuff in startup.pl ----
    use Apache::Reload;
    Thanks to all who replied, especially tilly.

    -Blake

Re: Redefining Exported Subroutines (esp. in mod_perl/mason)
by John M. Dlugosz (Monsignor) on Jun 19, 2001 at 02:17 UTC
    You could switch to some other dummy package before importing, and then the names would not conflict.
Re: Redefining Exported Subroutines (esp. in mod_perl/mason)
by John M. Dlugosz (Monsignor) on Jun 19, 2001 at 02:21 UTC
    You could define your own Exporter that did force the symbol in. Put that in another module that redefines the proper routines in Exporter:: (saving away the old ones, even chaining to them).

    Use it like this:

    use ForceExport; # hypothetical use My::Module 'testsub'; no ForseExport; # put it back the way it was.
      This will not and cannot work.

      See my update explaining what the problem really is (now that I read the question properly.)

      I actually tried defining my own import function (My::Module::import) before I posted. It didn't work the way I had hoped. Thanks though.

      -Blake