perl 闭包中局部变量的可见性与局部变量的可见性

vsikbqxv  于 2022-11-15  发布在  Perl
关注(0)|答案(3)|浏览(247)

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在以下位置不可用...
其中...描述在“局部子例程”中引用$x的行。
谁能解释这一点; Perl的本地子例程与Pascal的本地子例程有本质的不同吗?

jq6vz3qz

jq6vz3qz1#

问题中的术语“local subroutine”似乎指的是词法子例程。这些是私有子例程,只在定义它们的作用域(块)中可见,在定义之后;就像私有变量一样。
但它们是用mystate定义(或预先声明)为my sub subname { ... }
仅仅将sub subname { ... }写入另一个子例程中并不能使其成为“本地”(在任何版本的Perl中),但它的编译方式就像它是与另一个子例程一起编写的一样,并被放置在其包的符号表中(例如main::)。

  • 问题在标题中提到了closure,下面是对此的注解 *

在Perl中,闭包是程序中的一个结构,通常是一个标量变量,它引用了一个sub,并在创建时从其作用域携带环境变量。另请参见perlfaq 7中的一个条目。解释起来很混乱。例如:

sub gen { 
    my $args = "@_"; 

    my $cr = sub { say "Closed over: $args, my args: @_" }
    return $cr;
}

my $f = gen( qw(args for gen) );

$f->("hi closed");
# Prints:
# Closed over: args for gen, my args: hi closed

匿名子函数“封闭”了定义它的作用域中的变量,也就是说,当它的生成函数返回它的引用并离开作用域时,由于引用的存在,这些变量仍然存在。由于匿名子函数是在运行时创建的,每次调用它的生成函数并重新创建其中的词法时,anon子函数也是如此。因此返回的对anon-sub的引用使用了词法数据,否则这些数据将消失。
回到“局部”子函数的问题上,如果我们想在这个问题中引入实际的闭包,我们需要从outer子例程返回一个代码引用,比如

sub outer {
    my $x = 'x' . "@_";
    return sub { say "$x @_" }
}
my $f = outer("args");
$f->( qw(code ref) );   # prints:  xargs code ref

或者,根据主要问题,正如在v5.18.0中引入的以及从v5.26.0开始的稳定版本,我们可以使用命名词法(真正嵌套!)子例程

sub outer {
    my $x = 'x' . "@_";
    
    my sub inner { say "$x @_" };

    return \&inner;
}

在这两种情况下,my $f = outer(...);都有从outer返回的代码引用,它正确地使用了本地词法变量($x)及其最新值。
但是我们不能在outer中使用一个普通的名为sub的闭包

sub outer {
    ...

    sub inner { ... }  # misleading, likely misguided and buggy

    return \&inner;    # won't work correctly
}

这个inner是在编译时生成的,并且是全局的,所以它从outer中使用的任何变量的值都是第一次调用outer时的值。所以inner只有在下一次调用outer之前才是正确的--当outer中的词法环境被重新生成而inner没有被重新生成时。作为一个例子,我可以很容易地找到this post,并在perldiag中看到该条目(或将use diagnostics;添加到程序中)。
[2]在我看来,穷人的物品在某种程度上具有功能性和数据性,这些功能性和数据性是在另一个时间在别处制造的,并且可以与传递给它的数据一起使用(而且两者都可以更新)。

tktrz96b

tktrz96b2#

如果需要“本地”子进程,则可以根据所需的向后兼容级别使用以下选项之一:

  • 5.26以上:
my sub inner { ... }
  • 5.18以上版本:
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 );
Variable "$arg" will not stay shared at a.pl line 9.
123
123

5.18确实引入了词法(“本地”)子例程。

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 );
123
456

如果需要支持旧版本的Perl,可以使用以下代码:
第一个

cygmwpex

cygmwpex3#

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

......而这一个不会:

sub outer()
{
    my $x = 'x';   # just to make a simple example

    eval {
        sub inner($)
        {
            print "${x}$_[0]\n";
        }
    };

    inner('foo');;
}

相关问题