Использовать лексическую переменную, объявленную в цикле For, в блоке Continue

#perl

#perl

Вопрос:

У меня возникли проблемы с компиляцией одного из моих модулей с тем, что выглядит проблемой с областью видимости переменной, но я не понимаю почему.

Пример кода:

 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

А именно, похоже, что он жалуется на использование $file в continue блоке внизу. Как вы можете видеть, $file объявлена как лексическая переменная в строке 2, во внешнем цикле foreach (помеченный ПУТЬ).

Однако, основываясь на perldoc для continue, я ожидал бы, $file что она все еще будет в области видимости:

[…] она всегда выполняется непосредственно перед повторным вычислением условия, точно так же, как третья часть цикла for в C. Таким образом, ее можно использовать для увеличения переменной цикла, даже когда цикл был продолжен […]

Чтобы иметь возможность увеличивать переменную цикла, не должен ли блок continue обрабатываться как часть той же лексической области видимости, что и цикл, к которому он присоединен?

Чего мне не хватает?


Примечание: Этот модуль является классом Moo, поэтому, хотя у меня нигде нет явного use strict оператора, при использовании Moo мы включаем strict и warnings.

Ответ №1:

Ваша $path переменная все еще находится в области видимости внутри continue BLOCK , но содержимое внутри for БЛОКА находится вне области видимости, потому что вы достигли конца этого БЛОКА (вы нажали на конечную скобку / вышли из фигурных скобок). $path Переменная, однако, не находится внутри фигурных скобок, поэтому может быть видна внутри continue BLOCK (даже если она не видна за пределами этого).

Если бы синтаксис был таким:

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

тогда я ожидал бы, что $x будет видно. IIRC, в perl 6 есть подобные вещи, но в perl 5 continue BLOCK находится за пределами блока цикла и, следовательно, не видит лексических переменных внутри этого блока.

Комментарии:

1. Таким образом, в основном «переменная цикла» в этом контексте относится только к итератору, а не к чему-либо еще в теле цикла? Если continue нет доступа к лексическим переменным внутри цикла for, существует ли какой-либо другой очевидный метод обеспечения закрытия старого дескриптора файла после каждой итерации?

2. @BryKKan она автоматически закроется, когда выйдет за пределы области видимости и количество ссылок на нее упадет до 0.

3. Предлагаемый синтаксис на самом деле не помогает. Представьте 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. 1 Perl doesn't know at compile-time which of the loop block's variables will be declared when the continue block is entered — Я не учел этого, вероятно, потому, что моя переменная была объявлена в первой строке, и поэтому (по-видимому?) гарантированно существует. Конечно, как я теперь вижу, с точки зрения компилятора, это не относится к остальной части блока и даже может быть неверно для некоторых крайних случаев в первой строке (т. Е. условного выполнения).

2. Компилятор мог бы изучить код и определить, безопасно ли использовать переменную, но программистам это показалось бы еще более странным, чем текущая ситуация. Во-первых, программистам пришлось бы выполнить тот же анализ, что и компилятору, чтобы точно определить, когда переменная освобождается. Это может иметь значение, если оно содержит объект с деструктором.

3. Согласен. Я просто не рассматривал это с точки зрения того, кто пытается написать компилятор, пока вы не указали на это.

4. Мой последний комментарий, полагаю, указывает на то, что это было бы странно и с точки зрения программиста. То, что компилятору проще не обрабатывать это, не является достаточной причиной, чтобы не обрабатывать это.