Perl尽可能干净地调用具有显式附加范围的子例程引用

Gre*_*bet 3 perl

我希望能够写出如下内容......

call_with_scope({
    x => 47,
}, sub {
    printf "$x\n";
    printf "$y\n";
});
Run Code Online (Sandbox Code Playgroud)

其中$y在含有表达(或者词汇或动态取决于符号)的环境是约束.

我已经找到了一种方法,但它需要no strict "vars"在包含call_with_scope(...)call_with_scope使用的表达式中生效,eval以便在将控制转移到回调之前创建本地绑定.

有没有办法避免要求no strict "vars"在呼叫站点或参考和更改local变量的值而不诉诸eval?

为了完整起见,下面的代码片段实现call_with_scope并打印47然后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";
        }
    );
};
Run Code Online (Sandbox Code Playgroud)

Sch*_*ern 5

我正在尝试写一些类似Test :: LectroTest ...除了不使用源过滤器和类似的注释Property { ##[ x <- Int, y <- Int ]## <body> }...我想写一些像Property({x => gen_int, y => gen_int}, sub { <body> })where $x$ybody内部的东西在"实例化"时得到它们的值进行了一项财产测试.

您可以通过在调用者包中定义$x$y作为全局变量来完成此操作.

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

但这不容易本地化.使用全局变量污染调用者的命名空间可能会导致测试之间出现神秘的数据泄露.通常,在测试库中使用尽可能少的魔法; 用户将有足够的自己奇怪的魔法来调试.

相反,提供一个返回属性的函数.例如,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;
}
Run Code Online (Sandbox Code Playgroud)

测试看起来像:

use LectroTest;

test(
    { x => 42 }, sub { print p->{x} }
);
Run Code Online (Sandbox Code Playgroud)