Local variable visibility in closures vs. local `sub`s

U. *_*ndl 2 perl closures subroutine

Perl 5.18.2 accepts "local subroutines", it seems.

Example:

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

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

    inner('foo');
}
Run Code Online (Sandbox Code Playgroud)

Without "local subroutines" I would have written:

#...
    my $inner = sub ($) {
        print "${x}$_[0]\n";
    }

    $inner->('foo');
#...
Run Code Online (Sandbox Code Playgroud)

And most importantly I would consider both to be equivalent.

However the first variant does not work as Perl complains:

Variable $x is not available at ...

where ... describes the line there $x is referenced in the "local subroutine".

Who can explain this; are Perl's local subroutines fundamentally different from Pascal's local subroutines?

zdi*_*dim 7

问题中的术语“局部子例程”似乎指的是词汇子例程。这些是私有子例程,仅在定义它们的范围(块)内可见,在定义之后;就像私有变量一样。

\n

my但它们是用or定义(或预先声明)的state,如my sub subname { ... }

\n

仅仅编写sub subname { ... }另一个子例程的内部不会使其成为“本地”(在 Perl 的任何版本中),但它的编译就像与其他子例程一起编写一样,并放置在其包的符号表中(main::例如例子)。

\n
\n

该问题在标题中提到了关闭,这是对此的评论

\n

Perl 中的闭包是程序中的一个结构,通常是一个标量变量,带有对 sub 的引用,并且在其(运行时)创建时从其范围携带环境(变量)。另请参阅perlfaq7 条目。解释起来很乱。例如:

\n
sub gen { \n    my $args = "@_"; \n\n    my $cr = sub { say "Closed over: |$args|. Args for this sub: @_" }\n    return $cr;\n}\n\nmy $f = gen( qw(args for gen) );\n\n$f->("hi closed");\n# Prints:\n# Closed over: |args for gen|. Args for this sub: hi closed\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

回到“本地”潜艇的问题。如果我们想引入问题的实际闭包,我们需要从outer子例程返回一个代码引用,例如

\n
sub outer {\n    my $x = \'x\' . "@_";\n    return sub { say "$x @_" }\n}\nmy $f = outer("args");\n$f->( qw(code ref) );   # prints:  xargs code ref\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n
sub outer {\n    my $x = \'x\' . "@_";\n    \n    my sub inner { say "$x @_" };\n\n    return \\&inner;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

在这两种情况下,都会返回正确使用局部词法变量 ( ) 及其最新值的my $f = outer(...);代码引用。outer$x

\n

outer但是我们不能在闭包中使用普通的命名 sub

\n
sub outer {\n    ...\n\n    sub inner { ... }  # misleading, likely misguided and buggy\n\n    return \\&inner;    # won\'t work correctly\n}\n
Run Code Online (Sandbox Code Playgroud)\n

inner是在编译时创建的并且是全局的,因此它使用的任何变量都将在第一次调用outer时烘焙它们的值。outer因此只有在下次调用之前inner才是正确的——当 中的词法环境被重新创建但没有重新创建时。作为一个例子,我可以很容易地找到这篇文章,并查看perldiag 中的条目(或添加到程序中)。outerouterinneruse diagnostics;

\n
\n

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

\n

  • @simbabque你的意思是词汇替换?我真的只能想到能够在更大的子系统中很好地组织代码。(尽管我们确实有匿名子程序。)但是在文档中他们也提出了递归。我不确定拥有它有什么具体和明显的优势。好问题。 (2认同)