Perl调用子例程引用时,尽可能清晰地使用显式附加作用域

Perl调用子例程引用时,尽可能清晰地使用显式附加作用域,perl,Perl,我希望能写一些像下面这样的东西 call_with_scope({ x => 47, }, sub { printf "$x\n"; printf "$y\n"; }); 其中,$y在包含表达式的环境中绑定(根据符号的不同,可以是词汇上的,也可以是动态的) 我已经找到了一种方法,但是它要求没有严格的“vars”在包含call\u with\u scope(…)的表达式中有效,并且call\u with\u scope的实现在将控制权转移到回调之前使用eval创建本

我希望能写一些像下面这样的东西

call_with_scope({
    x => 47,
}, sub {
    printf "$x\n";
    printf "$y\n";
});
其中,
$y
在包含表达式的环境中绑定(根据符号的不同,可以是词汇上的,也可以是动态的)

我已经找到了一种方法,但是它要求
没有严格的“vars”
在包含
call\u with\u scope(…)
的表达式中有效,并且
call\u with\u scope
的实现在将控制权转移到回调之前使用
eval
创建本地绑定

有没有办法避免在呼叫现场要求
无严格的“变量”
,或者在不诉诸eval的情况下参考并更改
本地变量的值

为了完整起见,下面的代码片段使用_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";
        }
    );
};
我试着写一些类似于Test::electrotest。。。除了在执行属性测试的“实例化”时,不使用源过滤器和注释(如
Property{{x gen_int},sub{})
,其中
$x
$y
在body内部获得它们的值)之外

您可以通过在调用者的包中将
$x
$y
定义为全局变量来实现这一点

no strict 'refs';
my $caller = caller;
for my $var (keys %$properties) {
    *{$caller.'::'.$var} = $properties->{$var};
}
$code->();
但这不容易本地化。并且用全局变量污染调用方的命名空间可能会导致测试之间的神秘数据泄漏。一般来说,在测试库中使用尽可能少的魔法;用户将拥有足够的自己的奇怪魔法来调试

相反,提供一个返回属性的函数。例如,
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;
}
测试结果如下所示:

use LectroTest;

test(
    { x => 42 }, sub { print p->{x} }
);

问题是anon sub是在调用
call\u with\u scope
之前编译的,因此
call\u with\u scope
没有机会为该sub声明变量

你为什么不像其他子类一样使用参数

call_with_scope([ 47 ], sub {
   my ($x) = @_;
   printf "%s\n", $x;
   printf "%s\n", $y;
});
不再是了


如果您可以在sub之外声明
$x
,这里有一个替代方案

use strict;
use warnings;

use PadWalker qw( closed_over );

sub call_with_scope {
   my ($inits, $cb) = @_;
   my $captures = closed_over($cb);
   for my $var_name_with_sigil (keys(%$captures)) {
      my ($var_name) = $var_name_with_sigil =~ /^\$(.*)/s
         or next;

      $inits->{$var_name}
         or next;

      ${ $captures->{$var_name_with_sigil} } = $inits->{$var_name};
   }

   return $cb->();
}

{
   my $x;
   my $y = 48;
   call_with_scope({
      x => 47,
   }, sub {
      printf "%s\n", $x;
      printf "%s\n", $y;
  });
}
这是因为变量是在编译时创建的,并在范围退出时清除

如果
sub
编译在与调用
call\u with_scope
不同的作用域和包中,它甚至可以工作

{
   my $sub = do {
      my $x;
      my $y = 48;
      sub {
         printf "%s\n", $x;
         printf "%s\n", $y;
      }
   };

   call_with_scope({ x => 47 }, $sub);
}

但是你真的希望你的程序有这样的魔力吗?

这并不能完全满足你的需要,因为它不允许你设置值,但它可能是一个起点:这可能是个坏主意。它违反了子例程的封装。通常你只需将任何东西作为参数传入。你为什么要这样做?它解决了什么问题ng?可能有更好的方法来解决这个问题。@Schwern是的,我想是这样:\n我正试图写一些类似于Test::Electrotest的东西……除了不使用源代码过滤器和注释,比如在
属性{x gen_int},sub{})
其中
$x
$y
正文中
在执行属性测试的“实例化”时获取它们的值。(其中
gen_int
是生成新整数源的函数)@GregoryNisbet您可以在调用方的包中生成
$x
$y
全局变量,并使用类似
*{$var}=$property->{x}的符号引用
。但我建议在测试库中使用尽可能少的魔法,这是一件不那么神秘的事情要调试。可能只有一个全局变量,
$p
,它包含属性哈希。然后您可以
local$p=…
,用户可以编写
$p->{x}