tags:

views:

73

answers:

1

I need to localise some variables in another package, but I won't know what their names are until they are passed in. My attempts to use local with typeglobs didn't work, so I have fallen back to saving the value of the variable and restoring it manually. Is there a better way? Note, the error checking to see if a variable exists before mucking with it has been elided for clarity.

#!/usr/bin/perl

use strict;
use warnings;

my %orig;
for my $name (qw/foo bar baz/) {
    my $var = \${$meta::{$name}};
    $orig{$name} = $$var;
    $$var = $$var * 2;
}
meta::p();
for my $name (keys %orig) {
    my $var = \${$meta::{$name}};
    $$var = $orig{$name};
}
meta::p();

package meta;

BEGIN {
    our $foo = 1;
    our $bar = 2;
    our $baz = 3;
}

sub p { print join(" :: ", $meta::foo, $meta::bar, $meta::baz), "\n" }

I am trying to avoid an eval like this:

my $eval = '';

for my $name (qw/foo bar baz/) {
    $eval .= "local \$meta::$name = \$meta::$name * 2;\n";
}

eval "$eval meta::p()";
meta::p();

Is trying to avoid the eval a waste of time? Is the new code worse than the eval?

Note, I also don't want to use symbolic references, all code must work under strict. The current solution works, so I am not looking for hacks to get around what I am doing, I am looking for a better solution (if one exists).

+2  A: 

Turning off strict refs should allow you to do what you want with symbolic references. I don't have access to a system to do a test with ATM, but something like this should work:

use strict;
use warnings;

{
    no strict `refs`;
    local ${"meta::$name"} = ${"meta::$name"} * 2;

    meta::p();
}

meta::p();

Update:

After some testing, I found a snag.

While it is easy to use local on a single symbolic reference. But to localize an array of variable names is not so simple--the loop constructs (map, for, etc) all create a small, scope around the expression they operate on, which terminates localization.

# This works but does not work on an array of names.
{   no strict 'refs';

    local ( ${'meta::foo'}, ${'meta::bar'}, ${'meta::baz'} );
    meta::p();
}
meta::p();


# THIS DOES NOT WORK AT ALL!
{   no strict 'refs';

    my @to_localize = map "meta::$_", qw/foo bar baz/;

    local ${$_} = $$_ * 2 for @to_localize;
    meta::p();

}
meta::p();

The only solution I could find uses goto, which we all know is considered harmful.

{   no strict 'refs';

    my @to_localize = map "meta::$_", qw/foo bar baz/;

    LOCALIZER:
        my $localize_me = shift @to_localize;
        local ${$localize_me} = $$localize_me * 2;
        goto LOCALIZER if @to_localize;

    meta::p();

}
meta::p();

I'm open to better ideas.

daotoad
I should have put in there that I don't want to use symbolic references either.
Chas. Owens
I believe that daotoad method is mentioned in "Mastering Perl", or "Advanced Perl".
J.J.
@Chas Owens: Why not use symbolic refs? IMO, using symbolic references in a strictly controlled scope is reasonable. Many things that, at first, seem to require a string eval can be done by disabling strict refs or strict subs.
daotoad
If the variable name they passed in doesn't exist I don't want to created it. That means mucking with the %package:: anyway. I also don't like turning off strict. This solution works fine, but I was hoping I had missed something easy involving local and typeglobs.
Chas. Owens
@Chas Owens: If you don't know the variable name until runtime you have no choice but to use symrefs (your variable name *is* in a variable after all) or hand-roll the localization. Lexically disabling strict refs is okay when you understand what you're doing and why. Unfortunately, doing this for a list implies a loop which creates its own block and *that's* a problem. I can't think of a way around that other than string eval.
Michael Carman