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...
CodePudding user response:
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.
CodePudding user response:
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.