Perl - 哪些范围/闭包/环境产生这种行为?

发布于 2024-12-26 15:36:48 字数 1441 浏览 1 评论 0原文

给定一个根目录,我希望识别任何 .svn 目录和 pom.xml 的最浅父目录。

为了实现这一点,我定义了以下函数

use File::Find;
sub firstDirWithFileUnder {
    $needle=@_[0];
    my $result = 0;
    sub wanted {
        print "\twanted->result is '$result'\n";
        my $dir = "${File::Find::dir}";

        if ($_ eq $needle and ((not $result) or length($dir) < length($result))) {
            $result=$dir;
            print "Setting result: '$result'\n";
        }
    }
    find(\&wanted, @_[1]);
    print "Result: '$result'\n";
    return $result;
}

..并这样调用它:

    $svnDir = firstDirWithFileUnder(".svn",$projPath);
    print "\tIdentified svn dir:\n\t'$svnDir'\n";
    $pomDir = firstDirWithFileUnder("pom.xml",$projPath);
    print "\tIdentified pom.xml dir:\n\t'$pomDir'\n";

有两种情况出现,我无法解释:

  1. 当 .svn 搜索成功时,内部感知到的 $result 值嵌套子例程 wanted 持续到下一次调用 firstDirWithFileUnder 时。因此,当 pom 搜索开始时,尽管 my $result = 0; 行仍然存在,但 wanted 子例程将其值视为最后一个 firstDirWithFileUnder 的返回值调用。
  2. 如果 my $result = 0; 行被注释掉,则该函数仍然可以正确执行。这意味着 a) 外部作用域 (firstDirWithFileUnder) 仍然可以看到 $result 变量以便能够返回它,并且 b) print 显示wanted 仍然看到上次的 $result 值,即它似乎已经形成了一个闭包,该闭包在第一次调用 firstDirWithFileUnder 后仍然持续存在。

有人可以解释发生了什么,并建议我如何在进入外部范围时正确地将 $result 的值重置为零?

Given a root directory I wish to identify the most shallow parent directory of any .svn directory and pom.xml .

To achieve this I defined the following function

use File::Find;
sub firstDirWithFileUnder {
    $needle=@_[0];
    my $result = 0;
    sub wanted {
        print "\twanted->result is '$result'\n";
        my $dir = "${File::Find::dir}";

        if ($_ eq $needle and ((not $result) or length($dir) < length($result))) {
            $result=$dir;
            print "Setting result: '$result'\n";
        }
    }
    find(\&wanted, @_[1]);
    print "Result: '$result'\n";
    return $result;
}

..and call it thus:

    $svnDir = firstDirWithFileUnder(".svn",$projPath);
    print "\tIdentified svn dir:\n\t'$svnDir'\n";
    $pomDir = firstDirWithFileUnder("pom.xml",$projPath);
    print "\tIdentified pom.xml dir:\n\t'$pomDir'\n";

There are two situations which arise that I cannot explain:

  1. When the search for a .svn is successful, the value of $result perceived inside the nested subroutine wanted persists into the next call of firstDirWithFileUnder. So when the pom search begins, although the line my $result = 0; still exists, the wanted subroutine sees its value as the return value from the last firstDirWithFileUnder call.
  2. If the my $result = 0; line is commented out, then the function still executes properly. This means a) outer scope (firstDirWithFileUnder) can still see the $result variable to be able to return it, and b) print shows that wanted still sees $result value from last time, i.e. it seems to have formed a closure that's persisted beyond the first call of firstDirWithFileUnder.

Can somebody explain what's happening, and suggest how I can properly reset the value of $result to zero upon entering the outer scope?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

橘亓 2025-01-02 15:36:48

使用警告然后使用诊断会产生这些有用的信息,包括解决方案:

变量“$needle”不会在 ----- 第 12 行 (#1) 保持共享

(W 闭包) 内部(嵌套)命名子例程引用
在外部命名子例程中定义的词法变量。

当调用内部子程序时,它会看到
外部子例程的变量在 first 之前和期间
调用外部子程序;在这种情况下,在第一次调用之后
外部子程序完成后,内部和外部子程序将不再存在
不再共享变量的共同值。换句话说,
变量将不再被共享。

这个问题通常可以通过制作内部子程序来解决
匿名,使用 sub {} 语法。
当内部匿名 subs 时
创建外部子程序中的引用变量,它们
会自动反弹到此类变量的当前值。


$result 是词法范围的,这意味着每次调用 &firstDirWithFileUnder 时都会分配一个全新的变量。
sub Want { ... } 是一个编译时子例程声明,这意味着它由 Perl 解释器编译一次并存储在包的符号表中。由于它包含对词法作用域 $result 变量的引用,因此 Perl 保存的子例程定义将仅引用 $result 的第一个实例。第二次调用 &firstDirWithFileUnder 并声明一个新的 $result 变量时,这将是与 $result 内的 $result 完全不同的变量代码>&想要。

您需要将 sub Wanted { ... } 声明更改为词法作用域的匿名 sub:

my $wanted = sub {
    print "\twanted->result is '$result'\n";
    ...
};

并调用 File::Find::find 如下所示

find($wanted, $_[1])

,< code>$wanted 是子例程的运行时声明,并且在每次单独调用时都会使用对 $result 的当前引用重新定义&firstDirWithFileUnder


更新:此代码片段可能具有指导意义:

sub foo {
    my $foo = 0;  # lexical variable
    $bar = 0;     # global variable
    sub compiletime {
        print "compile foo is ", ++$foo, " ", \$foo, "\n";
        print "compile bar is ", ++$bar, " ", \$bar, "\n";
    }
    my $runtime = sub {
        print "runtime foo is ", ++$foo, " ", \$foo, "\n";
        print "runtime bar is ", ++$bar, " ", \$bar, "\n";
    };
    &compiletime;
    &$runtime;
    print "----------------\n";
    push @baz, \$foo;  # explained below
}
&foo for 1..3;

典型输出:

compile foo is 1 SCALAR(0xac18c0)
compile bar is 1 SCALAR(0xac1938)
runtime foo is 2 SCALAR(0xac18c0)
runtime bar is 2 SCALAR(0xac1938)
----------------
compile foo is 3 SCALAR(0xac18c0)
compile bar is 1 SCALAR(0xac1938)
runtime foo is 1 SCALAR(0xa63d18)
runtime bar is 2 SCALAR(0xac1938)
----------------
compile foo is 4 SCALAR(0xac18c0)
compile bar is 1 SCALAR(0xac1938)
runtime foo is 1 SCALAR(0xac1db8)
runtime bar is 2 SCALAR(0xac1938)
----------------

请注意,编译时 $foo 始终引用相同的变量 SCALAR(0xac18c0) ,并且这也是函数第一次运行的运行时 $foo

本示例中包含最后一行 &foo, push @baz,\$foo ,这样 $foo 就不会产生垃圾在 &foo 末尾收集。否则,第二个和第三个运行时 $foo 可能会指向相同的地址,即使它们引用不同的变量(每次声明变量时都会重新分配内存)。

Using warnings and then diagnostics yields this helpful information, including a solution:

Variable "$needle" will not stay shared at ----- line 12 (#1)

(W closure) An inner (nested) named subroutine is referencing a
lexical variable defined in an outer named subroutine.

When the inner subroutine is called, it will see the value of
the outer subroutine's variable as it was before and during the first
call to the outer subroutine; in this case, after the first call to the
outer subroutine is complete, the inner and outer subroutines will no
longer share a common value for the variable. In other words, the
variable will no longer be shared.

This problem can usually be solved by making the inner subroutine
anonymous, using the sub {} syntax.
When inner anonymous subs that
reference variables in outer subroutines are created, they
are automatically rebound to the current values of such variables.


$result is lexically scoped, meaning a brand new variable is allocated every time you call &firstDirWithFileUnder.
sub wanted { ... } is a compile-time subroutine declaration, meaning it is compiled by the Perl interpreter one time and stored in your package's symbol table. Since it contains a reference to the lexically scoped $result variable, the subroutine definition that Perl saves will only refer to the first instance of $result. The second time you call &firstDirWithFileUnder and declare a new $result variable, this will be a completely different variable than the $result inside &wanted.

You'll want to change your sub wanted { ... } declaration to a lexically scoped, anonymous sub:

my $wanted = sub {
    print "\twanted->result is '$result'\n";
    ...
};

and invoke File::Find::find as

find($wanted, $_[1])

Here, $wanted is a run-time declaration for a subroutine, and it gets redefined with the current reference to $result in every separate call to &firstDirWithFileUnder.


Update: This code snippet may prove instructive:

sub foo {
    my $foo = 0;  # lexical variable
    $bar = 0;     # global variable
    sub compiletime {
        print "compile foo is ", ++$foo, " ", \$foo, "\n";
        print "compile bar is ", ++$bar, " ", \$bar, "\n";
    }
    my $runtime = sub {
        print "runtime foo is ", ++$foo, " ", \$foo, "\n";
        print "runtime bar is ", ++$bar, " ", \$bar, "\n";
    };
    &compiletime;
    &$runtime;
    print "----------------\n";
    push @baz, \$foo;  # explained below
}
&foo for 1..3;

Typical output:

compile foo is 1 SCALAR(0xac18c0)
compile bar is 1 SCALAR(0xac1938)
runtime foo is 2 SCALAR(0xac18c0)
runtime bar is 2 SCALAR(0xac1938)
----------------
compile foo is 3 SCALAR(0xac18c0)
compile bar is 1 SCALAR(0xac1938)
runtime foo is 1 SCALAR(0xa63d18)
runtime bar is 2 SCALAR(0xac1938)
----------------
compile foo is 4 SCALAR(0xac18c0)
compile bar is 1 SCALAR(0xac1938)
runtime foo is 1 SCALAR(0xac1db8)
runtime bar is 2 SCALAR(0xac1938)
----------------

Note that the compile time $foo always refers to the same variable SCALAR(0xac18c0), and that this is also the run time $foo THE FIRST TIME the function is run.

The last line of &foo, push @baz,\$foo is included in this example so that $foo doesn't get garbage collected at the end of &foo. Otherwise, the 2nd and 3rd runtime $foo might point to the same address, even though they refer to different variables (the memory is reallocated each time the variable is declared).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文