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

时间:2017-10-01 01:00:33

标签: perl

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

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

$y绑定在包含表达式的环境中(词法或动态,取决于符号)。

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

有没有办法避免在呼叫站点要求no strict "vars"或者在不诉诸eval的情况下引用和更改local变量的值?

为了完整起见,下面的代码段实现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";
        }
    );
};

2 个答案:

答案 0 :(得分:5)

  

我试图写一些类似Test :: LectroTest的东西...除了使用源代码过滤器和Property { ##[ x <- Int, y <- Int ]## <body> }中的注释...我想写点东西比如Property({x => gen_int, y => gen_int}, sub { <body> }),当{&#34;实例化&#34;进行了一项财产测试。

您可以通过将$x$y定义为来电者套餐中的全局字段来实现此目的。

$x

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

相反,提供一个返回属性的函数。例如,$y

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

测试看起来像:

p

答案 1 :(得分:2)

问题是在调用call_with_scope之前编译了anon sub,因此call_with_scope没有机会为该子句声明变量。

你有没有像其他任何子一样使用参数?

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

已经不再了!

如果你能在子公司之外宣布$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_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);
}

但你真的想在你的计划中有这种魔力吗?