Reputation: 70303
Consider this script (boiled down from the much larger one I have):
#!/usr/bin/perl
use strict;
use warnings;
package help {
my %HELPTEXT;
$HELPTEXT{ "topic" } = "Some help\n";
sub get_help( $ ) {
print $HELPTEXT{ $_[0] };
}
}
package main {
sub main() {
help::get_help( "topic" );
}
}
main();
This works, prints Some help
.
Now, I would like to place the initialization ($HELPTEXT{ "topic" } = "Some help\n";
and others like it) at the end of the script -- while retaining package help
and its code where it is.
However, this doesn't work:
# ...
main();
package help {
$HELPTEXT{ "topic" } = "Some help";
}
Error message: Global symbol "%HELPTEXT" requires explicit package name (did you forget to declare "my %HELPTEXT"?)
This does not work either:
main();
$help::HELPTEXT{ "topic" } = "Some help\n";
Error message: Name "help::HELPTEXT" only used once
/ Use of uninitialized value in print
.
Apparently my understanding of Perl packages and their namespacing is lacking.
Can I have a variable in a mid-file package namespace, and still initialize it at end-of-file?
Added: I figured out that the declaration issue can be resolved via our
:
# ...
main();
package help {
our %HELPTEXT;
$HELPTEXT{ "topic" } = "Some help\n";
}
From perldoc:
"our" makes a lexical alias to a package (i.e. global) variable of the same name in the current package for use within the current lexical scope.
However, this still gives an error: Use of uninitialized value in print
. Even if I wrap the initialization in an INIT {}
block.
I'd like to not only have a solution for this, but understand why this isn't working as I expected. Coding by guessing feels bad...
Upvotes: 1
Views: 141
Reputation: 1832
I don't think you have any problem with package namespaces. You seem to be using them correctly. You're real issue seems to be variable scoping and order of execution.
Remember that Perl goes in two steps, compilation and execution. On the first pass compile time actions happen which include legalizing symbols and creating the basic flow. However none of those now legal variables are populated until runtime. Variables defined at the end of the script aren't filled until runtime reaches them. You can make it happen "right now" with INIT
or BEGIN
blocks as you found out.
You have two main options. The first and more elegant IMO is to have a subroutine that both populates and returns the help text that you place at the end but call from wherever. This uses the state
keyword which is awesome because it let's you declare data right where it's used. The one caveat is that it must be a scalar. state
declarations are only executed once at runtime (when you call the sub).
## the top
## thousands of lines of code
## the bottom
package help {
sub get_help( $ ) {
use feature qw/state/;
state $HELPTEXT = { topic1 => "You need more help", topic2 => "RTFM", };
print $HELPTEXT->{ $_[0] };
}
}
## Or
sub help::get_help( $ ) { ...
The other option is to put a BEGIN block at the bottom that will populate to data.
## the top
package help {
sub get_help( $ ) {
our %HELPTEXT;
print $HELPTEXT{ $_[0] };
}
}
## thousands of lines of code
## the bottom
package help {
BEGIN {
our %HELPTEXT = ( topic1 => "You need more help", topic2 => "RTFM", );
}
}
Remember that our
merely legalizes a global variable name in a scope. It doesn't create a new variable like my
does.
Upvotes: 1
Reputation: 132830
There are a few things to understand here, and I write quite a bit more about this in Learning Perl. Most of this is realizing that lexical and package variables are two different systems. Anything that is lexical doesn't care what the default package is.
Here's your example with the strict
failure:
use v5.12;
use strict;
package help {
$HELPTEXT{ "topic" } = "Some help";
}
The use strict
applies to whatever scope you employ it (file or block). In that scope, you have to either declare your variables on first use or use the full package specification. Since you do neither of those, you get the error. Changing the package doesn't turn it off.
The package
declaration merely changes the default package name. With the block form, it only changes the default package in that block. Anything lexical doesn't care, including lexical variables. This $foo
doesn't care what the default package is because it's not a package variable. It lasts until the scope (block or file) ends no matter what the default package is:
use v5.12;
use strict;
use warnings;
package help;
my $foo = 'bar';
package main;
say $foo; # outputs bar
Using our
instead has a curious effect when you combine packages in the same scope. You get a lexical variable with that name that is available anywhere in the scope and you get a package variable with that name (available anywhere in the program). They use the same data, so changing either changes the data and both versions show that:
use v5.12;
use strict;
use warnings;
package help;
our $foo = 'bar';
package main;
say $foo; # bar
say $help::foo; # bar
$help::foo = 'baz';
say $foo; # baz
say $help::foo; # baz
This doesn't work in the package BLOCK
form because the our
limits the lexical alias to that block, even though the package version is still there (and not limited to the block):
use v5.12;
use strict;
use warnings;
package help {
our $foo = 'bar';
}
say $help::foo; # bar
You could specify the full package specification, in which case strict
no longer cares:
use v5.12;
use strict;
package help {
$help::HELPTEXT{ "topic" } = "Some help";
}
Or, use vars
:
use v5.12;
use strict;
package help {
use vars qw(%HELPTEXT);
$HELPTEXT{ "topic" } = "Some help";
}
You wanted some help
package stuff at the top, then some more help
package stuff at the bottom. Since the package
just changes the default package, you can use it again. That is, you aren't limited to using it once and you aren't forced to put everything inside the first help
block:
use v5.12;
use strict;
use warnings;
package help {
use vars qw(%HELPTEXT);
sub help { $HELPTEXT{$_[0]} }
}
say "TOPIC: " . help::help("topic");
package help {
use vars qw(%HELPTEXT);
$HELPTEXT{ "topic" } = "Some help";
}
This doesn't work because these are all run time statements. Perl compiles all of this, but it's then going to execute the statements in order. That means that the $HELPTEXT{ "topic" } = "Some help";
doesn't run until the very end, after you try to use it.
A BEGIN
solves that:
#!/usr/bin/perl
use v5.12;
use strict;
use warnings;
package help {
use vars qw(%HELPTEXT);
sub help { $HELPTEXT{$_[0]} }
}
say "TOPIC: " . help::help("topic");
BEGIN {
package help {
use vars qw(%HELPTEXT);
$HELPTEXT{ "topic" } = "Some help";
}
}
When Perl compiles this, it reaches the BEGIN block and compiles it, but then runs its block immediately. The package variable for %HELPTEXT
is setup and available anywhere in the program. By the time the top package help {}
is run, that hash is already set up. It might be easier to see with some messages thrown in:
use v5.12;
use strict;
use warnings;
package help {
use vars qw(%HELPTEXT);
say "Setting up rest of help";
sub help { $HELPTEXT{$_[0]} }
}
say "TOPIC: " . help::help("topic");
BEGIN {
package help {
use vars qw(%HELPTEXT);
say "Setting up messages";
$HELPTEXT{ "topic" } = "Some help";
}
}
The output shows the order. The BEGIN
runs first, then the top pf the script, and then the middle:
Setting up messages
Setting up rest of help
TOPIC: Some help
But, if you had a lexical variable instead, something different happens. The package variable is there, but the lexical version masks it while it is in scope:
use v5.12;
use strict;
use warnings;
$help::foo = 'quux';
say "Before: $help::foo";
package help {
my $foo = 'bar';
say "Inside: $foo";
}
say "After: $help::foo";
The output shows that:
Before: quux Inside: bar After: quux
But, remember the lexical masking is only inside the block. If you call out to something not in that scope,
use v5.12;
use strict;
use warnings;
$help::foo = 'quux';
say "Before: $help::foo";
package help {
my $foo = 'bar';
say "Inside: $foo";
main::show_foo();
}
say "After: $help::foo";
sub show_foo { say "show_foo: $help::foo" }
The output shows that even when called inside the block, show_foo
uses the package version despite it that subroutine being in a different package:
Before: quux
Inside: bar
show_foo: quux
After: quux
Thus, the trick is to know if your variable is lexical (affected by scope) or package.
Upvotes: 2
Reputation: 385986
INIT
does work (though BEGIN
is more common).
#!/usr/bin/perl
use strict;
use warnings;
package help {
our %HELPTEXT;
$HELPTEXT{ "topic" } = "Some help\n";
sub get_help( $ ) {
print $HELPTEXT{ $_[0] };
}
}
package main {
sub main() {
help::get_help( "topic" );
}
}
main::main();
INIT {
package help;
our %HELPTEXT;
$HELPTEXT{ "topic" } = "Some help\n";
}
$ perl a.pl
Some help
I think you still had my %HELPTEXT;
in the first block?
Instead of using our
twice, you could use
use vars qw( %HELPTEXT );
In any case, what you are doing is a mess.
Upvotes: 1