ernix
ernix

Reputation: 3643

How to `local` multiple array elements at once?

I have some perl subroutines rely on passing variables in global.

So I'm trying to wrap the subroutine to receive arguments, then local these vars.

But looping over arguments, every elements are extracted into different code blocks.

Ho can I local these elements in one code block? I want to pass following test script:

#!/usr/bin/perl
use strict;
use warnings;
use Test::More;

our $foo = 'foo';
our $bar = 'bar';
our $buz = 'buz';

my %arg = (
    foo => 'FOO',
    bar => 'BAR',
    buz => 'BUZ',
);

subtest test1 => sub {
    no strict;

    note 'I need `local` some variables';

    local ${"::foo"} = 'FOO';
    local ${"::bar"} = 'BAR';
    local ${"::buz"} = 'BUZ';

    is $foo, 'FOO';
    is $bar, 'BAR';
    is $buz, 'BUZ';
};

subtest test2 => sub {
    no strict;

    note 'These variables are in %arg';
    note 'So I tried looping over it';

    for (keys %arg) {
        local ${"::${$_}"} = $arg{$_};
        is ${$_}, $arg{$_};
    }
};

subtest test3 => sub {
    no strict;

    note 'But how to get these localized variables in the same code block?';
    note 'To loop over the argument need code block.';

    for (keys %arg) {
        local ${"::${$_}"} = $arg{$_};
    }

    is $foo, 'FOO';
    is $bar, 'BAR';
    is $buz, 'BUZ';
};

subtest test4 => sub {
    is $foo, 'foo';
    is $bar, 'bar';
    is $buz, 'buz';
};

done_testing();

Outputs:

    # Subtest: test1
    # I need `local` some variables
    ok 1
    ok 2
    ok 3
    1..3
ok 1 - test1
    # Subtest: test2
    # These variables are in %arg
    # So I tried looping over it
    ok 1
    ok 2
    ok 3
    1..3
ok 2 - test2
    # Subtest: test3
    # But how to get these localized variables in the same code block?
    # To loop over the argument need code block.
    not ok 1
    #   Failed test at q.pl line 52.
    #          got: 'foo'
    #     expected: 'FOO'
    not ok 2
    #   Failed test at q.pl line 53.
    #          got: 'bar'
    #     expected: 'BAR'
    not ok 3
    #   Failed test at q.pl line 54.
    #          got: 'buz'
    #     expected: 'BUZ'
    1..3
    # Looks like you failed 3 tests of 3.
not ok 3 - test3
#   Failed test 'test3'
#   at q.pl line 55.
    # Subtest: test4
    ok 1
    ok 2
    ok 3
    1..3
ok 4 - test4
1..4
# Looks like you failed 1 test of 4.

I ended up creating stash to store them temporarily

subtest test3 => sub {
    no strict 'refs';

    note 'But how to get these localized variables in the same code block?';
    note 'To loop over the argument need code block.';

    my %stash;
    for (keys %arg) {
        ($stash{$_}, ${"main::$_"}) = (${"main::$_"}, $arg{$_});
    }

    is $foo, 'FOO';
    is $bar, 'BAR';
    is $buz, 'BUZ';

    for (keys %arg) {
        ${"main::$_"} = $stash{$_};
    }
};

Thank you for all respondents.

Upvotes: 3

Views: 89

Answers (2)

Colin Phipps
Colin Phipps

Reputation: 908

You can give up trying to drive your locals from a data structure and just list them out, as in your working example.

Or, stop trying to use local. As described in perlsub, all local does is store the current value of the variable in a hidden stack for the duration of the current scope. You could instead save the value yourself and restore it yourself at the end of your test.

Upvotes: 1

Shmuel Fomberg
Shmuel Fomberg

Reputation: 546

you can't loop over local, because with each loop the previous loop local is reversed.
If this is hash, you can do local $hash{@keys} to local multiple keys together. but this does not work on separated globals.

Upvotes: 1

Related Questions