A recent thread discussed about the possibility to use something like global variables in Perl, and how to do it. I don't want to discuss the the pros and cons of global variables in this post (but everyone is encouraged to contribute with their point of view, of course), but only a little toy module that derived from that thread.

Using plain package variables is a way to gain global variables, because they're accessible program-wide:

package A; print "the global k in package B is $B::k\n";
That is, if we fully qualify a variable, including its package, we're always allowed to access it. And yes, this works under strict too. OTOH, this approach is pretty weak with respect to typos.

The fact is that this really sucks. We're all lazy, and we don't want all that typing. One interesting suggestion is to code a sub, that can be easily made really global, so that we can use it wherever we need (see here):

package main; my $MW = ...; # Create your main window here. sub UNIVERSAL::MW {$MW}
This really scares me a bit. This is really globally pervasive, and I fear (but it's my ignorance, for sure) that it could break when we're using some modules. For example: what happens when we use AUTOLOAD too? I really don't know, but I suspect that half of the audience won't be happy. Moreover, this approach is subject to typos as well, because the compiler will accept a call to a function pretty happily.

That's why I thought about creating something that could be used like this (from the SYNOPSIS of vars::global):

# In the place/package where we want to create globals use vars::global create => qw( $foo @bar %baz ); # Add some more global symbols vars::global->create(qw( $hello @world %now )); # Somewhere else, where we need to access those globals use vars::global qw( $foo @bar %baz ); # Don't try to use globals that do not exist use vars::global qw( $Foo ); # typo, croaks use vars::global qw( @inexistent ); # we don't create by default # use 'create' as above # You can also import and create new globals use vars::global qw( $foo %baz ), create => qw( $hey @joe ); # If you're lazy, you can import all the globals defined so far use vars::global ':all';
The rationales are the following: Here comes the implementation:
package vars::global; { use version; $VERSION = qv('0.0.1'); use warnings; use strict; use Carp; # Module implementation here # Where we keep existing globals my %ref_for; sub import { my $package = shift; my $caller = caller; my @import; GRAB_IMPORTS: while (@_) { my $param = shift; last GRAB_IMPORTS if lc($param) eq 'create'; if (lc($param) eq ':all') { @import = keys %ref_for; while (@_) { last GRAB_IMPORTS if shift eq 'create'; } last GRAB_IMPORTS; } push @import, $param; } $package->_create($caller, @_); $package->_import($caller, @import, @_); return; } ## end sub import sub create { my $package = shift; my $caller = caller; $package->_create($caller, @_); $package->_import($caller, @_); return; } ## end sub create sub has { my $package = shift; my ($symbol) = @_; return unless exists $ref_for{$symbol}; return $ref_for{$symbol}; } ## end sub has sub _create { my $package = shift; my $caller = shift; my @symbols = @_; no strict 'refs'; no warnings 'once'; foreach my $symbol (@symbols) { # Some checks croak "undefined symbol" unless defined $symbol; croak "empty symbol" unless length $symbol; my $identifier = substr $symbol, 1; croak "invalid identifier '$identifier'" unless $identifier =~ /\A \w+ \z/mxs; my $fqn = $package . '::' . $identifier; my $sigil = substr $symbol, 0, 1; $ref_for{$symbol} = $sigil eq '$' ? \${$fqn} : $sigil eq '@' ? \@{$fqn} : $sigil eq '%' ? \%{$fqn} : croak "invalid sigil: '$sigil'"; } ## end foreach my $symbol (@symbols) return; } ## end sub _create sub _import { my $package = shift; my $caller = shift; no strict 'refs'; foreach my $symbol (@_) { my $ref = $package->has($symbol) or croak "non existent global: '$symbol'"; *{$caller . '::' . substr $symbol, 1} = $ref; } return; } ## end sub _import } 1; # Magic true value required at end of module __END__
The "core" of the import is the following line:
*{$caller . '::' . substr $symbol, 1} = $ref;
which strips the sigil out of $symbol to get the variable identifier, prepends the package name (in $caller) with the colons and defines the symbol in the symbol table. Something basically equal to what happens in vars itself, but I have to credit MJD for having learnt it (e.g. here).

We keep a hash %ref_for to remember which global variables have been created so far. New items are added only when the create() method is called, either explicitly or using the create parameter in the import list. For this reason, the import() method first selects the symbols that have to be simply imported from those that require creation too, so that you can write something like:

use vars::global qw( $these @exists ), create => qw( $those @are %new );
One can check if a symbol already exists with the has() method, which returns a reference to the global variable (if this exists) or undef.

The two methods _import() and _create() factor out the code, so that we can call them from both import() and create(). They cannot call each other, because otherwise caller would be spoiled by the added call. Instead, we factor the code out, call caller from the "public" method (that without the underscore) and then pass this as a parameter to the "private" one (that with the underscore).

Conclusion: I had fun writing it... but shall I ever use it? I doubt :)

Flavio
perl -ple'$_=reverse' <<<ti.xittelop@oivalf

Don't fool yourself.

Replies are listed 'Best First'.
Re: Global variables
by Joost (Canon) on Oct 02, 2006 at 20:01 UTC
    I don't really see the advantage of this over Exporter:
    package Foo; use base 'Exporter; our @EXPORT_OK = qw(foo_this foo_that); sub foo_this { ... }
    use Foo qw(foo_this foo_that);
    Having to specify the exporting package is a good thing IMO, since it clarifies where the functions/vars are defined and it makes it possible to re-use "global" names in/from different packages. Both can be useful if your program is growing.

    The only downside to Exporter is that it can get a bit verbose. I kind of like the syntax in Perl6::Export::Attrs.

    update: s/Perl6::Export/Perl6::Export::Attrs/;

      No real advantage, maybe disadvantages. The very difference is that in your approach the programmer has to create package Foo explicitly, while in my toy I wanted to let the user define globals using some "service" from a module. This is probably where the "disadvantage" creeps in: the approach in vars::global is probably too easy, and allows uncontrolled spread of global variables definitions, while in your technique the programmer is kind of obliged to confine all globals together, which leads to better control (and less global proliferation, by application of lazyness).

      If my technique were useful... I think we would have had vars::global in CPAN a long, long time ago ;)

      Flavio
      perl -ple'$_=reverse' <<<ti.xittelop@oivalf

      Don't fool yourself.
        The very difference is that in your approach the programmer has to create package Foo explicitly, while in my toy I wanted to let the user define globals using some "service" from a module.
        So in what package are your non-global subroutines defined? :-) update: I mean; you're not defining all your code in the same package, since that would make vars::global unnessicary. But if you're using logical packages anyway, you can use those to export from.
        This is probably where the "disadvantage" creeps in: the approach in vars::global is probably too easy...
        Yeas... but my biggest gripe is that by constricting all globals to vars::global you can't structure your globals into logical packages. I mean, vars::global::query() isn't really as descriptive as MyDatabase::query() or CGI::query()

        I must confess to using main:: as a global namespace sometimes, especially when I'm experimenting, but I try to move as much code as possible into logical classes/packages when the structure becomes clear.

Re: Global variables
by bobf (Monsignor) on Oct 02, 2006 at 21:35 UTC

    I agree with Joost's recommendation for using subs for global variables. In addition to the points he made, this approach makes it easy to control the write status of the variable in different parts of your code, simply by creating one sub for an accessor and another for a mutator. Packages that import only the accessor will have read-only access, while others can have write access by also importing the mutator (assuming, of course, that the mutator is allowed to be exported in the first place).

Re: Global variables
by Anonymous Monk on Oct 03, 2006 at 07:39 UTC
    Moreover, this approach is subject to typos as well, because the compiler will accept a call to a function pretty happily.

    What's the point you are trying to make here? This issue has nothing to do with global variables, and your only defense here is to not use named subroutines. If you want to use a subroutine you should use a reference to an anonymous function. Note however that if you use 'strict', and call your subroutines without parenthesis, the compile will bark if your subroutine is mistyped. But you can't always easily leave the parenthesis off.

      What's the point you are trying to make here? This issue has nothing to do with global variables, and your only defense here is to not use named subroutines.
      Well, I'm trying to have fun and learn something ;). Moreover, I was discussing about the opportunity to use named subroutines instead of global variables (I linked this thread in my OP as a start), so I suppose this has to do with global variables.
      If you want to use a subroutine you should use a reference to an anonymous function.
      I'm not able to contextualise this within the discussion. The whole point of using subroutines instead of plain variables is - ehr - not using plain variables. If I have to use anonymous functions, I'm probably obliged to use a variable to carry the reference.

      My consideration was about using functions instead of global variables, and I do agree with you about the fact that the compiler will croak if we try to call them without using parentheses - something along the lines of use constant. On the other side, this would make it difficult (probably not impossible, but I'm no expert here) to use them as variables instead of constants (which is why you probably say that you can't always easily leave the parenthesis off I suppose).

      Flavio
      perl -ple'$_=reverse' <<<ti.xittelop@oivalf

      Don't fool yourself.