Home > Net >  Understanding Perl packages namespacing
Understanding Perl packages namespacing

Time:11-04

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.

  • Related