Perl: conditional use and scope
A reader asks
If I conditionally load a perl module, do those module variables get passed to the whole perl script?
if ( some_test ) { use "perlmodule_001"; } else { use "perlmodule_002"; }Are the elements of either perl module available outside the
ifstatement?
The main program from the question has a syntax error:
syntax error at prog0 line 2, near "use "perlmodule_001""
Perl’s documentation for use explains:
use Module
Imports some semantics into the current package from the named module, generally by aliasing certain subroutine or variable names into your package. It is exactly equivalent toBEGIN { require Module; Module->import( LIST ); }except that Module must be a bareword.
Note the bareword constraint at the end: the compiler doesn’t like the
double quotes around the argument to use. Our friend was likely
thinking of the older require operator that does accept
strings and arbitrary expressions in general.
Say we have two modules with alternative definitions of $Foo and $Bar:
package Perlmodule_001;
use Exporter 'import';
our @EXPORT = qw/ $Foo $Bar /;
our $Foo = 'apple';
our $Bar = 'orange';
1;
and
package Perlmodule_002;
use Exporter 'import';
our @EXPORT = qw/ $Foo $Bar /;
our $Foo = 42;
our $Bar = 'w00t!';
1;
Note the use of Perlmodule_001, for example, rather than perlmodule_001: the
perlmodlib documentation notes, “Perl informally reserves
lowercase module names for pragma modules like integer and
strict.”
Consider the following simple driver:
#! /usr/bin/perl
use warnings;
use strict;
if (@ARGV && $ARGV[0] eq "two") {
use Perlmodule_002;
}
else {
use Perlmodule_001;
}
sub maybeUndef {
defined $_[0] ? $_[0] : "<undefined>";
# got 5.10?
# $_[0] // "<undefined>";
}
print "Foo = ", maybeUndef($Foo), "\n",
"Bar = ", maybeUndef($Bar), "\n";
It uses maybeUndef to explicitly show when a value is undefined and
also to silence potential undefined-value warnings.
The program seems to run as intended
$ ./prog1 Foo = apple Bar = orange
but the output is the same even when an argument of two is supplied on
the command line!
$ ./prog1 two Foo = apple Bar = orange
The good news is that the imported variables are in scope for the rest
of the program, as indicated in the above documentation for use
(with emphasis added):
Imports some semantics into the current package from the named module …
To understand why we never see Perlmodule_002’s $Foo and $Bar,
note that use “is exactly equivalent to” require at
BEGIN time, and the perlmod documentation explains exactly
when that is (with added emphasis):
A
BEGINcode block is executed as soon as possible, that is, the moment it is completely defined, even before the rest of the containing file (or string) is parsed.
So the compiler sees use Perlmodule_002 and processes it. Then it sees
use Perlmodule_001 and processes it. When the compiler finishes
digesting the rest of the code, it’s time for the execution phase, when
the @ARGV check finally takes place. As written, Perlmodule_001 will
always win!
Because ordinary modules affect the current package, use–ing an
ordinary module inside a conditional block is entirely misleading. I was
careful to qualify the previous statement for ordinary modules because
the effects of some pragmatic modules (e.g., strict and
integer—note the lowercase names!) are limited tightly to the
enclosing block only.
The fix is to process @ARGV at BEGIN time and conditionalize the
module imports with the equivalent require and import:
#! /usr/bin/perl
use warnings;
use strict;
BEGIN {
if (@ARGV && $ARGV[0] eq "two") {
require Perlmodule_002;
Perlmodule_002->import;
}
else {
require Perlmodule_001;
Perlmodule_001->import;
}
}
sub maybeUndef {
defined $_[0] ? $_[0] : "<undefined>";
# got 5.10?
# $_[0] // "<undefined>";
}
print "Foo = ", maybeUndef($Foo), "\n",
"Bar = ", maybeUndef($Bar), "\n";
An alternative is protecting use with eval as in
BEGIN {
if (@ARGV && $ARGV[0] eq "two") {
eval "use Perlmodule_002";
}
# ...
so a particular use runs only when control reaches its eval but is
ignored otherwise. This is a safe, sensible use of eval.
Either way, the program now does what we expect!
$ ./prog2 Foo = apple Bar = orange $ ./prog2 two Foo = 42 Bar = w00t!
You might wonder why the code has to be inside a BEGIN block after the
use lines are conditionalized. If you have the strict pragma
enabled—and you should!—it wants variables to be imported and declared
before execution begins. Otherwise, compilation will fail because for
all it knows, $Foo and $Bar in the main package were typos.