Beefy Boxes and Bandwidth Generously Provided by pair Networks
Syntactic Confectionery Delight
 
PerlMonks  

Conditional loading of module with global exports

by learnedbyerror (Monk)
on Feb 22, 2016 at 19:26 UTC ( [id://1155832]=perlquestion: print w/replies, xml ) Need Help??

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

Spoiler - the author of this message is an idiot - signed said idiot

In the poor idiots defense, he blindly put his faith in "use strict" and trusted that any message issued by it was "gospel". This includes believing it when says that a Global Symbol name does not exist that it indeed does not exist. I need to do more research on this, but it appears "use strict" does not know when a symbol has been exported via an import following a require.

But this good faith gaff alone doesn't raise one to the ignoble title of idiot; however, creating a test case with two scripts containing the snippet "Key => $0" in two different files where this "Key" determines how the module in question persists data and where the issue is about data persistence surely does! Yes, I am an idiot!!

So my thanks to the original responder - Anonymous Monk on Feb 22, 2016 at 20:58 UTC. Your guidance would have worked properly "if" I had not subtly, but totally, screwed up my test case!! Thank you kind Monk.

Oh Knowledgeable Ones,

My perl knowledge is proven yet again insufficient. I kindly ask your indulgence in answering me the following:

I am creating a script to download pages from various web sites and save the pages to a local file. I want to have the option to either persist the list in a form that could be shared by multiple instantiations of this script using IPC::Lite or to have it run independently using a non-persisted list in RAM. Furthermore, I want to be able to execute the script giving it a list to use in RAM and have it not interact at all with the persisted lists

I have used IPC::Lite for many years and am aware of its pros and cons. In the past, I have always loaded via use

use IPC::Lite qw(@urls); push(@urls,'...');

This approach loads the modules and exports @urls at compile time.

However, in this script I prefer to load it at run time depending upon the command line options that I pass. I have tried to use require; however, I receive a compilation error that the exported variable, @urls in this case, is not declared.

Please see the following example

require IPC::Lite; IPC::Lite->import( Key => $0, qw( @urls ) ); push(@urls, '...'); Results in: Global Symbol "@urls" requires explicit package name (did you forget t +o declare "my @urls"?)

As I researched my problem, I found an alternative approach using the use method of UNIVERSAL::require; however, it failed similarly

require UNIVERSAL::require; my $module = "IPC::Lite"; $module->use( Key => $0, qw( @urls ) ); push(@urls,'...'); Results in: Global Symbol "@urls" requires explicit package name (did you forget t +o declare "my @urls"?)

The only option that I see at this time is to create separate packages with accessors to totally abstract the list access. I often use this mechanism on larger scripts and realize its value from an isolation and maintenance perspective; however, this is perl dammit and TIMTOWTDI :)

More importantly than just wanting to do it my way, I suspect that this points to a deficit in my perl knowledge and I want to make sure that I understand how to conditionally load modules that export globals.

Please accept my gracious thanks in advance for helping me address this deficit in my knowledge

lbe

Replies are listed 'Best First'.
Re: Conditional loading of module with global exports
by kcott (Archbishop) on Feb 22, 2016 at 21:22 UTC

    G'day learnedbyerror,

    You can conditionally load modules using the if pragma.

    Command line options will be processed at runtime, which will be too late to affect compile-time loading of modules. However, you can modify the command line in a different way, by setting an environment variable, which will be available at compile time.

    Here's a minimal example (pm_1155832_cond_use_mod.pl) using List::Util:

    #!/usr/bin/env perl -l use strict; use warnings; # Compile time use if $ENV{PM_1155832_USE}, 'List::Util' => qw{max}; BEGIN { print 'Check for List::Util::max() at compile time'; eval { max(1, 2) }; print $@ if $@; } # Runtime if (! $ENV{PM_1155832_USE}) { require List::Util; List::Util->import(qw{max}); } print 'Max is ', max(1, 2);

    Load module at runtime:

    $ pm_1155832_cond_use_mod.pl Check for List::Util::max() at compile time Undefined subroutine &main::max called at ./pm_1155832_cond_use_mod.pl + line 11. Max is 2

    Load module at compile time:

    $ PM_1155832_USE=1 pm_1155832_cond_use_mod.pl Check for List::Util::max() at compile time Max is 2

    — Ken

      Ken, good catch! I had not thought about using an environment variable. While not my exact preference of handling it via the command line parameters, it does provide a way to handle from the command line.

      I will give this a try!

      lbe
Re: Conditional loading of module with global exports
by Tanktalus (Canon) on Feb 22, 2016 at 22:36 UTC

    Since IPC::Lite is just pushing the variable into your namespace as a global variable, I'd think a fully-scoped name would eliminate the error:

    use IPC::Lite (); # always load it, why not? if ($saving) { IPC::Lite->import( Key => $0, qw(@urls) ); } push @::urls, '...';
    Of course, if your namespace isn't main, then substitute appropriately, e.g., @LearnedByError::urls. Or just turn off the warning for undeclared variables around the code that uses @urls - to some people this is the "right" solution, though I'd prefer avoiding it where possible.

    Though, to be honest, I'm surprised our @urls before the IPC::Lite->import... doesn't work.

      Tanktalus, thank you for your post. Yours was the post that made my synapses start firing to make me realize that I was living up to my erstwhile name - learnedbyerror

Re: Conditional loading of module with global exports
by choroba (Cardinal) on Feb 22, 2016 at 23:27 UTC
    Declaring the variable with our seems to work for me. Check below where the variables are declared.

    I created the following module:

    package MyVar; use warnings; use strict; use Exporter qw{ import }; our @ARR1 = qw( a b c ); our @ARR2 = qw( D E F ); our @EXPORT = qw( @ARR1 ); our @EXPORT_OK = qw( @ARR2 ); __PACKAGE__

    And this is the code that uses it. Argument processing is in a BEGIN block to happen at compile time, so that @list is populated when if is called:

    #!/usr/bin/perl use warnings; use strict; our @ARR1; use Getopt::Long; my @list; BEGIN { GetOptions('arrays|a=s' => \ my $arrays); my $list = { one => ['@ARR1'], two => ['@ARR2'], both => [qw[ @ARR1 @ARR2 ]], }->{$arrays}; @list = @{ $list || [] }; } use if scalar @list => ('MyVar', @list); our @ARR2; print @ARR1, ' ', @ARR2, "\n";

    Try running it as

    example.pl -a no example.pl -a one example.pl -a two example.pl -arrays=both

    ($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,

      Monk choroba, thank you for your example. While Tanktalus' post got me thinking, you example made me realize that I had screwed something up for sure. It was only after reading your post that I went back to scratch and created a new test case very similar to yours that worked. Thank You very much! - lbe

Re: Conditional loading of module with global exports
by Anonymous Monk on Feb 22, 2016 at 20:58 UTC

    Simple untested thought: Did you try actually declaring the variable?

    require IPC::Lite; our @urls; IPC::Lite->import( Key => $0, qw( @urls ) ); push(@urls, '...');

      Updated!! Oh Anonymous Monk, please accept my apologies for maligning your good, accurate and valuable advice. Yes, this did work when tested properly

      Yes, I did try this. The script will compile; however, the data that is persisted in IPC::Lite is not accessible as @url is the RAM @url.

      See the following examples

      This code will persist the results using IPC::Lite

      use IPC::Lite Key => 'TEST', qw(@urls) ; push( @urls, ("a", "b", "c") );

      This code verifies that the data persists

      use IPC::Lite Key => 'TEST', qw(@urls) ; foreach ( @urls ) { print "$_\n"; }; OUTPUT: a b c

      This code uses the method that you referred

      require IPC::Lite; our @urls; IPC::Lite->import( Key => $0, qw( @urls ) ); foreach ( @urls ) { print "$_\n"; }; OUTPUT:

      There is no output because the variable accessed is the RAM variable

      Thanks for the thought

Re: Conditional loading of module with global exports
by dasgar (Priest) on Feb 22, 2016 at 21:01 UTC

    Would the if pragma provide you with the functionality that you want? From the documentation: "The if module is used to conditionally load or unload another module."

      UPDATE:My response below is not fully correct. The if pragma can work if combined with an environment variable. See post from Ken for details

      lbe

      Original below:

      No, it will not. I considered this also. The if pragma is applied at compile time, not at run time. Since Getopt::Long has not yet processed the commend line, I do not yet have the information needed to provide the conditional for the if pragma.

        Doing a search, I found Module::Load with the following description from its documentation: "runtime require of both modules and files". Based on that description, it sounds like something worth checking out.

Re: Conditional loading of module with global exports
by mhearse (Chaplain) on Feb 22, 2016 at 21:57 UTC

      Do tell, I am not a mind reader :)

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1155832]
Front-paged by GotToBTru
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others drinking their drinks and smoking their pipes about the Monastery: (3)
As of 2024-04-18 04:12 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found