Perl 5.18.2 似乎接受“本地子例程”。
示例:
sub outer()
{
my $x = 'x'; # just to make a simple example
sub inner($)
{
print "${x}$_[0]\n";
}
inner('foo');
}
如果没有“本地子例程”,我会写:
#...
my $inner = sub ($) {
print "${x}$_[0]\n";
}
$inner->('foo');
#...
最重要的是,我认为两者是等效的。
然而,第一个变体不起作用,正如 Perl 抱怨的那样:
变量 $x 不可用于...
其中
...
描述了“本地子例程”中引用的行。谁能解释一下; Perl 的局部子例程与 Pascal 的局部子例程有根本不同吗?
”似乎指的是词法子例程。这些是私有子例程,仅在定义它们的范围(块)内可见,在定义之后;就像私有变量一样。 但它们是用
$x
或
my
定义(或预先声明)的,如 state
仅仅在另一个子例程中编写 my sub subname { ... }
并不会使其成为“本地”(在 Perl 的任何版本中),但它的编译就像与其他子例程一起编写一样,并放置在其包的符号表中(
例如sub subname { ... }
)。
perlfaq7 条目。解释起来很乱。例如:
main::
†
回到“本地”潜艇的问题。如果我们想引入问题的实际闭包,我们需要从 sub gen {
my $args = "@_";
my $cr = sub { say "Closed over: |$args|. Args for this sub: @_" }
return $cr;
}
my $f = gen( qw(args for gen) );
$f->("hi closed");
# Prints:
# Closed over: |args for gen|. Args for this sub: hi closed
outer
或者,根据主要问题,如 v5.18.0中所述并从
v5.26.0 开始稳定,我们可以使用命名词法(真正嵌套!)子例程
sub outer {
my $x = 'x' . "@_";
return sub { say "$x @_" }
}
my $f = outer("args");
$f->( qw(code ref) ); # prints: xargs code ref
sub outer {
my $x = 'x' . "@_";
my sub inner { say "$x @_" };
return \&inner;
}
都有从 my $f = outer(...);
返回的代码引用,它正确使用局部词汇变量 (
outer
) 及其最新值。
但是我们不能在
$x
中使用普通的命名 sub 来作为闭包
outer
这个 sub outer {
...
sub inner { ... } # misleading, likely misguided and buggy
return \&inner; # won't work correctly
}
是在编译时创建的,并且是全局的,因此它使用 inner
中的任何变量都将从第一次调用
outer
时烘焙它们的值。因此,只有在下次调用
outer
之前,
inner
才是正确的——此时
outer
中的词汇环境被重新创建,而
outer
却没有。作为一个例子,我可以很容易地找到this post
,并查看 perldiag中的条目 (或将
inner
添加到程序中)。
†如果您想要“本地”子系统,您可以根据您想要的向后兼容性级别使用以下选项之一:
use diagnostics;
5.18+:
my sub inner { ... }
“任意”版本:
use experimental qw( lexical_subs ); # Safe: Accepted in 5.26.
my sub inner { ... }
但是,您不应该使用
local *inner = sub { ... };
sub inner { ... }
基本相同
sub f { ... }
所以
BEGIN { *f = sub { ... } }
基本上是
sub outer {
...
sub inner { ... }
...
}
如您所见,
BEGIN {
*outer = sub {
...
BEGIN {
*inner = sub { ... };
}
...
};
}
甚至在inner
之外也是可见的,所以它根本不是“本地”的。
正如您所看到的,对
outer
的赋值是在编译时完成的,这引入了另一个主要问题。
*inner
use strict;
use warnings;
use feature qw( say );
sub outer {
my $arg = shift;
sub inner {
say $arg;
}
inner();
}
outer( 123 );
outer( 456 );
5.18 确实引入了词汇(“本地”)子例程。Variable "$arg" will not stay shared at a.pl line 9.
123
123
use strict;
use warnings;
use feature qw( say );
use experimental qw( lexical_subs ); # Safe: Accepted in 5.26.
sub outer {
my $arg = shift;
my sub inner {
say $arg;
};
inner();
}
outer( 123 );
outer( 456 );
如果需要支持旧版本的 Perl,可以使用以下命令:123
456
use strict;
use warnings;
use feature qw( say );
sub outer {
my $arg = shift;
local *inner = sub {
say $arg;
};
inner();
}
outer( 123 );
outer( 456 );
我从
man perldiag
所以这将是一个可能的解决方案: Variable "%s" is not available
(W closure) During compilation, an inner named subroutine or eval
is attempting to capture an outer lexical that is not currently
available. This can happen for one of two reasons. First, the
outer lexical may be declared in an outer anonymous subroutine
that has not yet been created. (Remember that named subs are
created at compile time, while anonymous subs are created at run-
time.) For example,
sub { my $a; sub f { $a } }
At the time that f is created, it can't capture the current value
of $a, since the anonymous subroutine hasn't been created yet.
...虽然这个不会:
sub outer()
{
my $x = 'x'; # just to make a simple example
eval 'sub inner($)
{
print "${x}$_[0]\n";
}';
inner('foo');;
}