闭包中的局部变量可见性与局部`sub`s

问题描述 投票:0回答:3

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 的局部子例程有根本不同吗?

perl closures subroutine
3个回答
7
投票
局部子例程

”似乎指的是词法子例程。这些是私有子例程,仅在定义它们的范围(块)内可见,在定义之后;就像私有变量一样。 但它们是用

$x

my
定义(或预先声明)的,如
state
仅仅在另一个子例程中编写 

my sub subname { ... }

并不会使其成为“本地”(在 Perl 的任何版本中),但它的编译就像与其他子例程一起编写一样,并放置在其包的符号表中(

例如sub subname { ... }
)。


问题在标题中提到了

closure,这是对此的评论 Perl 中的闭包

是程序中的一个结构,通常是一个标量变量,带有对 sub 的引用,并且在其(运行时)创建时从其范围携带环境(变量)。另请参阅其中的

perlfaq7 条目。解释起来很乱。例如: main::

匿名子“关闭”其定义范围内的变量,从某种意义上说,当其生成函数返回其引用并超出范围时,由于该引用的存在,这些变量仍然存在。 由于匿名子程序是在运行时创建的,每次调用其生成函数并重新创建其中的词法时,匿名子程序也会重新创建,因此它始终可以访问当前值。 因此,返回的对 anon-sub 的引用使用了词汇数据,否则这些数据就会消失。一点点魔法。

回到“本地”潜艇的问题。如果我们想引入问题的实际闭包,我们需要从 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 添加到程序中)。


在我看来,在某种程度上,这是一个穷人的对象,因为它具有功能和数据,是在其他时间在其他地方制作的,并且可以与传递给它的数据一起使用(并且两者都可以更新)

如果您想要“本地”子系统,您可以根据您想要的向后兼容性级别使用以下选项之一:

2
投票

5.26+:
  • 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 );
    
我从

0
投票
找到了一个相当不错的解释:

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');;
}


© www.soinside.com 2019 - 2024. All rights reserved.