【问题标题】:Use Lexical Variable Declared in For Loop in Continue Block在 Continue 块中使用 For 循环中声明的词法变量
【发布时间】:2019-03-27 22:31:55
【问题描述】:

我在编译我的一个模块时遇到了问题,这看起来是一个变量范围问题,但我不明白为什么。

示例代码

PATH: foreach my $path (@paths) {
    open(my $file, '<', $path) or next PATH;

    my %properties;
    LINE: while (<$file>) {
        $_ =~ /$property_regex/ or next LINE;
        $properties{$1} = $2;
    }

    foreach (@property_keys) {
        unless ($properties{$_}) {
            # do stuff
            next PATH;
    }

    if ( $irrelevant_condition ) {
        # do stuff
        next PATH;
    }

    foreach my $new_var (@new_list) {
        # do stuff (does not iterate PATH loop)
    }
} continue {
    if (defined $file) { close($file) or die; }
}

翻译成上面的精简代码,我得到的错误是:

全局符号“$file”在第 25 行需要明确的包名

也就是说,它似乎在抱怨在底部的continue 块中使用$file。如您所见,$file 在第 2 行被声明为一个词法变量,位于外部 foreach 循环(标记为 PATH)内。

但是,基于perldoc for continue,我希望$file 仍在范围内:

[...] 它总是在条件即将被再次评估之前执行,就像 C 中 for 循环的第三部分一样。因此它可以用来增加循环变量,即使循环已继续[...]

为了能够增加循环变量,是否将 continue 块视为与其附加到的循环相同的词法范围的一部分?

我错过了什么?


注意:这个模块是一个 Moo 类,所以虽然我在任何地方都没有明确的 use strict 声明,但 when you use Moo we enable strict and warnings

【问题讨论】:

    标签: perl


    【解决方案1】:

    您的 $path 变量仍在 continue BLOCK 的范围内,但 for 的 BLOCK 内的内容超出范围,因为您已到达该 BLOCK 的末尾(您已到达末尾大括号/ 退出大括号)。但是,$path 变量不在大括号内,因此可以在 continue BLOCK 内可见(即使除此之外不可见)。

    如果语法是这样的:

    for (...)
    {
     $x = stuff();
     continue { more_stuff($x) }
    }
    

    那么我希望$x 可见。 IIRC,perl 6 有这样的东西,但在 perl 5 中,continue BLOCK 在循环块之外,因此在该块内看不到词法变量。

    【讨论】:

    • 所以基本上“循环变量”在这种情况下只指迭代器,而不是循环体内的其他任何东西?如果continue 无法访问 for 循环内的词法变量,是否还有其他明显的方法可以确保在每次迭代后关闭旧文件句柄?
    • @BryKKan 当它超出范围并且它的引用计数下降到 0 时,它会自动关闭。
    • 建议的语法实际上并没有帮助。想象一下for (...) { next; my $x = 13; continue { ... $x ... } }。我的意思是,我们仍然可以这样做,但它引入了另一种未定义行为的情况。更糟糕的是,它非常容易意外调用。也许那个领先的next 最初并不存在,但它后来被添加......每当出现continue 块时,我认为将它放在循环块中会多么好,但这不会不行。
    【解决方案2】:

    保证循环变量在评估continue 块之前已声明,以便可以将变量提供给continue 块。

    但是循环块的任何部分都可以被跳过(例如使用next),所以Perl在编译时不知道当进入continue块时哪个循环块的变量会被声明,所以它不能使它们中的任何一个对continue 块可用。


    如果您对文件句柄自动关闭不满意(例如,因为您想检测写入错误),您可以使用范围保护而不是 continue 块。

    use Sub::ScopeFinalizer qw( scope_finalizer );
    
    for my $qfn (@qfns) {
       my $fh;
       my $guard = scope_finalizer {
          if ($fh) {
             close($fh)
                or die("Error writing to \"$qfn\": $!\n");
          }
       };
    
       open($fh, '>', $qfn)
          or die("Can't create \"$qfn\": $!\n");
    
       ...
    }
    

    【讨论】:

    • +1 Perl doesn't know at compile-time which of the loop block's variables will be declared when the continue block is entered - 我没有考虑过,可能是因为我的变量是在第一行声明的,因此(似乎?)保证存在。当然,正如我现在所看到的,从编译器的角度来看,该块的其余部分并非如此,甚至对于第一行的某些边缘情况(即条件执行)可能也不正确。
    • 编译器可以研究代码并确定使用变量是安全的,但这对程序员来说会比目前的情况更奇怪。一方面,程序员必须进行与编译器相同的分析,才能准确确定变量何时被释放。如果它包含一个带有析构函数的对象,这可能很重要。
    • 同意。在您指出这一点之前,我只是没有从试图编写编译器的人的角度考虑它。
    • 我最后的评论是想指出从程序员的角度来看这也很奇怪。让编译器更简单地不处理它并不是不处理它的理由。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-05-28
    • 1970-01-01
    • 1970-01-01
    • 2021-12-22
    • 2012-02-01
    相关资源
    最近更新 更多