Gregory Nisbet Gregory Nisbet - 3 years ago 131
Perl Question

Perl calling subroutine reference with explicit additional scope as cleanly as possible

I'd like to be able to write something like the following...

call_with_scope({
x => 47,
}, sub {
printf "$x\n";
printf "$y\n";
});


Where
$y
is bound in the environment containing the expression (either lexically or dynamically depending on the symbol).

I've found a way to do it, but it requires
no strict "vars"
to be in effect in the expression containing
call_with_scope(...)
and the implementation of
call_with_scope
uses
eval
to create local bindings before transferring control to the callback.

Is there a way to avoid either requiring
no strict "vars"
at the call site or refer to and change the value of a
local
variable without resorting to eval?

For completeness, the code snippet below implements
call_with_scope
and prints
47
and then
48
.

#!/usr/bin/env perl
use strict;
use warnings;

sub call_with_scope {
my ($env, $func) = @_;
my %property;
my @preamble;
foreach my $k (keys %$env) {
$property{$k} = $env->{$k};
# deliberately omitted: logic to ensure that ${$k} is a well-formed variable
push @preamble, "local \$$k = \$property{'$k'};";
}
# force scalar context
do {
my $str = join('', 'no strict "vars";', @preamble, '$_[1]->();');
return scalar(eval($str));
};
}

do {
no strict 'vars';
local $x;
my $y = 48;
call_with_scope(
{
x => 47,
},
sub {
printf "$x\n";
printf "$y\n";
}
);
};

Answer Source

I'm trying to write something kind of like Test::LectroTest ... except that instead of using a source filter and comments like in Property { ##[ x <- Int, y <- Int ]## <body> } ... I want to write something like Property({x => gen_int, y => gen_int}, sub { <body> }) where $x and $y inside body get their values when an "instantiation" of a property test is performed.

You can do this by defining $x and $y as globals in the caller's package.

no strict 'refs';
my $caller = caller;
for my $var (keys %$properties) {
    *{$caller.'::'.$var} = $properties->{$var};
}
$code->();

But this can't be easily localized. And polluting the caller's namespace with globals potentially leads to mysterious data leaking between tests. In general, use as little magic as possible in a test library; the user will have enough of their own weird magic to debug.

Instead, provide a function which returns the properties. For example, p.

package LectroTest;

use Exporter qw(import);
our @EXPORT = qw(test p);
our $P;

sub test {
    my($props, $test) = @_;

    local $P = $props;
    $test->();
}

sub p {
    return $P;
}

And the test looks like:

use LectroTest;

test(
    { x => 42 }, sub { print p->{x} }
);
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download