Beefy Boxes and Bandwidth Generously Provided by pair Networks
Clear questions and runnable code
get the best and fastest answer
 
PerlMonks  

BEGIN and compile-time

by jbert (Priest)
on Nov 02, 2006 at 10:16 UTC ( [id://581865]=perlmeditation: print w/replies, xml ) Need Help??

Hi.

I recently wanted to load another version of a module from a module of the same name. i.e. I wanted to load "../ModX.pm" instead of "./ModX.pm" (changing the load path wasn't an option at the time).

I tried this (in ./ModX.pm):

BEGIN { delete $INC{'ModX.pm'}; use lib '../'; use ModX; }
but this just kept the loaded version (./ModX.pm) loaded. After trying a few things, I found this worked:
BEGIN { delete $INC{'ModX.pm'}; use lib '../'; require '../ModX'; # Yes, I know I haven't done import }
Which I found odd, because perldoc -f use says that use is exactly equivalent to: BEGIN { require Module; import Module LIST; } To some extent I was happy, but I was still curious.

Some more playing around and I reaslised my error (which is probably obvious to many, but not to some, hence this post).

use is exactly equivalent to the above, and the issue is the nested BEGIN blocks. The effective code being run (ignoring imports) is:

BEGIN { delete $INC{'ModX.pm'}; use lib '../'; BEGIN { require 'ModX.pm'; } }
and so...the inner BEGIN block executes before the contents of the enclosing block, so you get the effect of something like:
BEGIN { require 'ModX.pm'; delete $INC{'ModX.pm'}; use lib '../'; }
where the require does nothing (because we have not yet frobbed %INC).

I thought this interesting. My error was in thinking of 'compile time' versus 'run time'. Everything I was doing was at 'compile time' so I wasn't thinking in terms of that.

Does anyone have anything to offer regarding:

  • A better way to do this (other than changing include paths in the environment/command line)
  • A way to also perform the 'import' stage of the use?
  • Any hidden problems with this approach?
Thanks for reading :-)

Replies are listed 'Best First'.
Re: BEGIN and compile-time
by Anonymous Monk on Nov 02, 2006 at 11:35 UTC
    BEGIN { delete $INC{'ModX.pm'}; use lib '../'; } use ModX;
    This will execute the BEGIN block before the use.

      You might as well move out the use lib as well.

      BEGIN { delete $INC{'ModX.pm'}; } use lib '../'; use ModX;

      That way, the statements will execute in the same order as they are in the source.

      Thanks, that looks nicer. Is there a way of capturing and replaying the original list passed to 'use'?

      ah, but...it looks like the top-level use will call import afterwards for us (so we don't need to worry about import...it Just Works with the require). In which case maybe require is more correct, since otherwise we'll call import twice, once when we use it (with no args) and once from the original, script-level use once we finish?

        If you want to use a use without import to be called, just put some parens after the module (and nothing inside the parens):
        use ModX ();
Re: BEGIN and compile-time
by Mutant (Priest) on Nov 02, 2006 at 11:01 UTC
    For the import, can't you just call it yourself after the require?
    import Modx @foo;
    You might get some 'subroutine redifined' warnings, but it should work.

    Some people might have a problem with you doing this at all (i.e. it's kind of an odd thing to dynamically load modules like this), but if you have a valid reason, go for it :)

    The usage of BEGIN{} etc. is a pretty common place to go wrong. It took me a while to get my head around it (and not sure I even have now).
      What would I put in @foo?

      Nod on the BEGIN{} issues. I'm just used to thinking of run-time versus compile-time overall, but the more correct way is to think of run-time and compile-time as a per-block thing. The outer begin block has its run-time at the script's compile-time. The inner begin block has its run-time at the compile-time of the outer begin block.

      Big fleas have little fleas, upon their backs to bite 'em
      Little fleas have lesser fleas, and so, ad infinitum.

        but the more correct way is to think of run-time and compile-time as a per-block thing.

        Quite so, ++ (Per-statement, even, in the case of use.)

        print("first"); BEGIN { delete $INC{'ModX.pm'}; use lib '../'; use ModX; } print("last");

        gets executed in the following order

        use lib '../'; # Before "delete" because of "use" use ModX; # Before "delete" because of "use" delete $INC{'ModX.pm'}; # Before "first" because of "BEGIN" print("first"); print("last");

        In detail:

        1. Compile print("first");.
        2. Compile BEGIN { ... }.
          1. Compile delete $INC{'ModX.pm'};.
          2. Compile use lib '../';.
          3. Execute use lib '../';.
          4. Compile use ModX;.
          5. Execute use ModX;.
        3. Execute BEGIN { ... }.
          1. Execute delete $INC{'ModX.pm'};.
        4. Compile print("last");.
        5. "Run phase" starts.
        6. Execute print("first");.
        7. Execute print("last");.
        Sorry, that code snippet wasn't very clear... @foo is just the list of what you want to import.
Re: BEGIN and compile-time
by cephas (Pilgrim) on Nov 02, 2006 at 15:02 UTC
    I apparently don't understand what you mean by not being able to modify the load path, since your executing use lib '../'; which is modifying @INC.

    Why don't you just do

    use lib '../'; use ModX;

    and be done with it?

    Update: After re-reading the original post, it seems you are trying to perform magic within ./ModX.pm to replace itself with ../ModX.pm. Any particular reason you don't just replace it then?
      The reason is that changes over time are likely to ../ModX.pm, which a copy won't keep up to date with.

      Until a robust way of changing everything to use ../ModX.pm directly is deployed, it is useful to have ./ModX.pm proxy directly to the live ./ModX.pm

      (Oh...and symlinks aren't likely to survive the version control system.)

Re: BEGIN and compile-time
by perrin (Chancellor) on Nov 02, 2006 at 17:36 UTC
    "When they BEGIN, the BEGIN..." Apologies to Cole Porter.
      Heh. Would you believe this is my actual snippet which gave me the aha moment when I ran it? :-)
      BEGIN { print "begin\n"; BEGIN { print "the beguine\n"; } }
Re: BEGIN and compile-time
by Firefly258 (Beadle) on Nov 09, 2006 at 03:43 UTC
    If the modules report version numbers (which normally is expected of modules), it might be best to use the following form of use;
    use Module VERSION # e.g. use Foo::Bar 5.01; # <- just v5.01 please, TYVM # or better still eval "use Foo::Bar 5.01" or do { use Foo::Bar } # fallback incase the 'required' version isnt available
    That way, your script works regardless of whether the module is moved about on the filesystem or your script is used on another machine.

      That doesn't do what you expect. The second use Foo::Bar gets executed first, and it gets executed unconditionally.

      Why would you want to fallback to the same module anyway? That's the same as not putting a version in the first place! If you wanted to fallback to a different module, the following will do:

      BEGIN { my $module = 'Foo::Bar'; require Foo::Bar; eval { Foo::Bar->VERSION(5.01) }; if ($@) { warn(...); $module = 'Foo::Baz'; require Foo::Baz; } import $module qw( ... ); }

      VERSION is documented in UNIVERSAL.

        You're right, and i seem to have overseen the fact that use; statements take execution precedence. Moreover, this particular eval() doesn't work too well as a truth assertion (blaming use) and the subsequent use; gets executed no matter what.

        Why fallback to the same module?? Well, maybe we do need it but ideally we'd like to take control and load the one version of it identified by a supplied version number but if the module at that version isn't available, prepare the script for the standard version and continue, something along the lines of OP's wants.

        So, as has already been examined, "use lib '../'; use Foo::Bar;" seems to have worked simply and elegantly.. but "use lib" places entries at the beginning of @INC and what if non-standard versions of ../strict.pm or ../warnings.pm existed? The user might inadvertently load these modules (OUCH!!) rather than the ones in the original @INC. Placing entries at the beginning of @INC is a bad idea, the risk of polluting the package with bogus code as a result of namespace clashes is real.
        { use lib ".."; use strict; # "../strict.pm" loaded instead of the real one }
        Placing entries at the end of @INC means that modules if found in the original @INC take precendence and are loaded instead of the ones you want loading. The chances are of such a case are slim but entirely possible especially if the user isn't aware of the perl module namespace. Even otherwise, the user might be patching/updating the an already existant module and can ensure the testing release is loaded by using it's $VERSION (wherever the new .pm is located via @INC). Placing entries at the end of @INC is generally safer.

        As an added benefit, using the $Foo::Bar::VERSION to validate loading Foo::Bar also ensures that your code is guaranteed to run under Foo::Bar, alternate versions of the module might break something.

        So if the OP wanted to load a non-standard Foo::Bar (let's assume v5.00) instead of a standard Foo::Bar (v5.01) and provided the version numbers reported by the 2 modules were different (they really ought to be) ...
        #!/usr/bin/perl -W BEGIN { push @INC, ".." } use strict; # "../strict.pm" untouched eval "use Foo::Bar 5.01 qw| raz baz taz |"; # try loading v5.01 if ($@) { # if loading v5.01 failed warn "@_"; # fallback to using standard (v5.00) use Foo::Bar qw| baz |; # raz and taz arent valid tags here } ...
      I managed to find out why my initial  eval " use ..." attempt failed. use; cannot be used in expressions . Hence, it does not return a value (so it's practically useless in truth evaluation constructs) even on a successful run like in the following..
      eval "use Foo::Bar" or do { warn "eval failed" }
      Here eval"" always evaluates to false as use; returns nothing and we end up with the do BLOCK always being executed.

      But getting eval"" to return a TRUE value on a successful eval changes that.
      eval "use Foo::Bar; 1" or do { warn "eval failed" }
        It is more appropriate to test $@.
        eval "use Foo::Bar"; if ($@) { warn "Eval failed: $@"; }
        Ikegami, you're right as always. Perhaps  eval "use Foo 5.01; 1" or do { require Foo; ... } does it.

        Thanks tye for clearning up that little something, it made me take a closer look at sub VERSION.

        Here's an attempt to get  use Module VERSION to load the right VERSION.
        $ cat a/strict.pm package strict; our $VERSION = 1.04; sub VERSION { my ($module, $version) = @_; return $VERSION unless $version; until ($VERSION == $version) { shift @INC; delete $INC{"strict.pm"}; eval " require strict; 1; "; $VERSION = $strict::VERSION; } return $VERSION; } $ cat ./strict #!/usr/bin/perl -Wl BEGIN { eval { local @INC = @INC; unshift @INC, qw|a b c|; eval " use strict 1.03 qw|subs vars refs|; 1; "; warn "$@" if $@; }; } print "strict using $INC{'strict.pm'} version $strict::VERSION "; $ ./strict strict using /usr/share/perl/5.8/strict.pm version 1.03
        It's kludgy and noisy but it works.

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others sharing their wisdom with the Monastery: (9)
As of 2024-04-19 16:39 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found