#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. Мой последний комментарий, полагаю, указывает на то, что это было бы странно и с точки зрения программиста. То, что компилятору проще не обрабатывать это, не является достаточной причиной, чтобы не обрабатывать это.