我希望能够写出如下内容……
call_with_scope({ x => 47,},sub { printf "$x\n"; printf "$y\n"; });
其中$y绑定在包含表达式的环境中(词法或动态,具体取决于符号).
我找到了一种方法,但它不需要在包含call_with_scope(…)的表达式中生效严格的“变量”,并且call_with_scope的实现在将控制转移到回调之前使用eval创建本地绑定.
有没有办法避免在调用站点要求没有严格的“变量”,或者在不诉诸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"; } ); };
解决方法
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 likeProperty({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.
您可以通过在调用者的包中将$x和$y定义为全局变量来实现此目的.
no strict 'refs'; my $caller = caller; for my $var (keys %$properties) { *{$caller.'::'.$var} = $properties->{$var}; } $code->();
但这不容易本地化.使用全局变量污染调用者的命名空间可能会导致测试之间出现神秘的数据泄露.通常,在测试库中使用尽可能少的魔法;用户将有足够的自己奇怪的魔法来调试.
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} } );