Perl Pragma Primer (2/2)
[previous] |
Perl Pragma Primer
Compile-Time Hints
On the previous page, I mentioned that the use
statement is executed during
the compile-phase of a script, and that standard modules can therefore export their various
symbols (variables, functions, etc.) so they're available anywhere in the current
package. As we've now learned, several of the pragmas only have an
effect on the current code block. What is it about these pragmas that causes this difference
in scope?
It turns out that most pragma modules are simple code blocks, containing
only a few lines of codes and which concentrate their efforts almost entirely on the manipulation
of global variables. One such key variable, which can be accessed via $^H
1,
is a key bitmap that the compiler consults as it compiles code; its various bits serve as
flags that tell the compiler whether strict-ness is on or off (and at what level), whether
strings should be treated as utf8 by character related operations, etc. In other words,
this variable contains hints that the compiler uses to know how the user would like their code
compiled, and with what specific features enabled. The import
routine of several pragmas
therefore only needs to alter key bits of this single global variable; the compiler then does
the bulk of the work when it compiles the remaining code according to the resulting
compiler hints settings.
The sharp-eyed among you may have noticed that I just said that $^H
is a global variable; and that's true. In fact, $^H
is one of the
few special truly global variables in Perl shared across all scripts, packages
and modules. A change to one of these variables changes it throughout your application! That
being the case, how is it that some pragmas--which we just learned accomplish their goals by manipulating
this global variable--are only effective for the block they are called in? If their import
routines change this variable, then won't it be in effect for the remainder of the
application?
It would--if not for the fact that Perl steps in and applies a rule especially to the compiler-hints flag (and others, such as the warning bits flag) during code compilation. Specifically, as Perl compiles a block of code, it stores the existing setting for the compiler-hints flag and then restores this saved value when it finishes compiling the block. Knowing this, the pragma can alter the value within the block and be assured that its residual effects will not remain outside the block--since the compiler effectively undoes its changes as soon as the block is complete, in other words:
# before compiling the following block,
# the interpreter saves the current value of $^H.
if ($foo) {
# The strict pragma now changes the settings of
# $^H, enabling strict-checking
use strict;
my $bar = 1;
my $baz = 2;
my $foobar = $bar + $baz;
# ... etc.
}
# Now that the block is compiled, the interpreter
# restores the original settings for $^H and continues;
# strict checking is therefore no longer in place
This mechanism is also what allows the pragmas to use the lexically scoped no
directive; i.e., you can use no
to turn off a pragma within a code
block, with the default state of the pragma restored at the end of the block. i.e.:
# Set strict checking in our script
use strict;
my $foo = 1;
if ($foo) {
# but turn it off for this block
no strict;
# no error here, since strict is turned off
$bar = 2;
print $bar;
}
# This will produce a compiler error, since
# strict is still enforced outside the block
$baz = 3;
print $baz;
Conditional Pragmas, Revisited
Let's put these various tidbits of information together and see if we can create a solution
to the original problem: How do we conditionally load the utf8
pragma so its effects remain in place for the remainder of the script?
As we discovered at the end of the previous page, placing the use
statement in a conditional block doesn't help us. The pragma is executed during compile-time, which
is what we want; but its effects don't remain outside the block it is called within. Further,
the actual condition won't be evaluated until the script is run, making the entire construct
moot. Let's examine some other possibilities which are equally ineffective. See if you can spot the
problems in these before the explanation is given.
BEGIN {
if ($]
This is just another way of saying the same thing. If you haven't seen it before, in Perl a
BEGIN
block is executed as soon as it has been completely
compiled (even before the remaining code of the script is compiled2). This example solves one of our
problems--namely that the code is fully compiled and therefore the if logic will be properly examined and
executed; but our other problems remain. The use
statement is still enclosed within a block. It
won't be effective outside of it; it's executed as the BEGIN
block
is being compiled, and would be ineffective as soon as the BEGIN
block
finished compiling, anyway.
if ($] import();
}
require
is an alternate code inclusion method; like use
it allows us to
pull an external chunk of code into our own script. Significantly unlike use
, is
the fact that require
statements are executed at runtime, not compile-time.
Also, a require
won't automatically call the fetched code's import
function,
which explains why we had to add that step in the example above. Since it's a runtime construct, the
above require
based snippet solves both our problem of needing the code to execute to properly
evaluate the conditional as well as bypasses the problem with the compile-time hints flag being restored
when the block is complete (which only happens as the code is compiled). Unfortunately, this last point
also means that the code is compleletely ineffective; since it's the manipulation of the compile-time hints
settings during code compilation which actually triggers the effect of the pragma in the first
place!
Solution: Combine BEGIN
and require
The solution to the problem combines the two approaches above:
BEGIN {
if ($] import();
}
}
When the compiler hits this block, it saves the current setting of the compiler-hints flag. It then
proceeds to compile the BEGIN
block itself. Since the pragma is being loaded via a
require
and not a use
, the compiler does not immediately execute
the utf8
pragma code; it simply compiles it and moves on.
When the compilation of the BEGIN
block is complete, the compiler-hints flag is restored
to its original state (the state it was in before the compilation of the BEGIN
block
started). At this point, the compiler sees that compilation of the BEGIN
block is finished
and proceeds to execute it immediately, before the remaining code of the script is compiled. Our conditional
is evaluated, the utf8
pragma is loaded (assuming we're on a Perl interpreter with a version
number less than 5.8), and its import
function called. The import
function, in
turn, modifies the compiler-hints flag to enable utf8-based processing. With the execution of the
BEGIN
block now completed, the interpreter returns to the process of compiling the remainder
of the script--but now with the compiler-hints flag set the way we want it. Whew!
An Alternate: use if
An alternate, more elegant solution to the problem presents itself in the form of another
pragma: if
. The if
pragma can be used
to load a module (including another pragma) based on a supplied condition, which is exactly what
we need to do here. use if
takes a pair of arguments, the condition, and the
module (optionally combined with its needed arguments) expressed as a hash. i.e.:
use if CONDITION, MODULE => ARGUMENTS;
or in our case:
use if ($]
Note that utf8
is a string argument to the if
pragma here, which is why
it's quoted.
So why not just use if
instead of the relatively long winded BEGIN
and
require
method? While the if
pragma is a more elegant solution to
the problem, it doesn't appear to be available (by default) in Perl versions prior to version 5.8; the
exact versions I'm testing for in my condition in the first place. If use if
is available on your system, you might prefer to use it, instead. See the
perldoc documentation of your system for more detail.
Conclusion
I often find it helpful in my own development efforts to take extra time to learn why something doesn't work as well as figuring out why the correct solution does; and it was exactly that type of analysis that led to the information presented in this article. I encourage you to perform the same type of analysis in your own projects, since the information you uncover may prove to be beneficial in other types of tasks and scripts--information you might not have known when you started.
For further information on Perl pragmas in general I refer you to your perldoc
documentation, specifically:
perldoc perlmodlib
perldoc perllexwarn
perldoc perlvar
If you're unfamiliar with perldoc
, my earlier primer
on the subject should bring you up to speed.
As always, good luck with your own Perl projects!
1. But probably shouldn't be; that's what the pragmas are for.
2. And conversely an
END
block is executed as late as possible in a script, typically when the script is shutting down.
[previous] |
Created: May 2, 2008
Revised: May 2, 2008
URL: https://webreference.com/programming/perl/pragmas/2.html