Wednesday, February 10, 2010

Perl: особенности цикла foreach

Цикл foreach в Perl всегда "локализует" переменную цикла, будь то глобальная переменная или переменная, объявленная с помощью my:
my $x = 1;
for $x (0..3) {}
print $x;  # 1, а не 3


Для сравнения сишный for:
my $x = 1;
for (; $x < 10; $x++) {}
print $x;  # 10, что логично


Еще одна особенность первого примера - отсутствие предупреждений при ре-декларации переменной цикла:

use warnings;

for my $x (0..3) {
my $x = "foo";
print $x;
}



Для сравнения:
use warnings;

{
my $x = 0;
my $x;
}
"my" variable $x masks earlier declaration in same scope

12 comments:

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

    ReplyDelete
  2. Вообще то именно это я и ожидал :)
    Для того, чтобы ограничить область видимости переменной можно использовать for my $x (2..3) {}

    ReplyDelete
  3. Вот пример:
    my $x = 1;
    for $x (@arr) { }
    $x = 14;
    $arr[12] = 8;
    Чему равно значение $x в случае, если $x не локализуется в цикле?

    ReplyDelete
  4. Если в массиве 13 элементов, то видимо 8.
    Imho эта фича из разряда "просто надо запомнить". И таких в перле много ;-)

    ReplyDelete
  5. Это разные переменные, что можно увидеть воспользовавшись Devel::Peek. Фактически получается, что в записи for my $x (1 .. 2) {}; слово my можно опустить.

    ReplyDelete
  6. Поведение действительно несколько магическое, не вполне ожидаемое. Это документированная фича перла. Для того, чтобы у вас не возникало проблем в понимании некоторых конкретных аспектов перла в будущем советую прочитать всё, что есть в perldoc perl. Там вы найдете полное оглавление документов для перл. Есть также очень удобный сайт "perldoc.perl.org".
    Кстати, вот выдержка из perldoc perlsyn (синтаксис перла):
    Foreach Loops
    The "foreach" loop iterates over a normal list value and sets the variable VAR to be each element of the list in turn. If the variable is preceded with the keyword "my", then it is exically scoped, and is therefore visible only within the loop. Otherwise, the variable is implicitly local to the loop and regains its former value upon exiting the loop. If the variable was previously declared with "my", it uses that variable instead of the global one, but it's still localized to the loop. This implicit localisation occurs only in a "foreach" loop.
    Для справки: объявленная локалная динамическая переменная замещает одноименные переменные в любых областях видимости, пока выполняется код в той области видимости, в которой эта переменная обявлена динамической.
    Не стоит путать локальную динамическую переменную с локальной лексической. Последняя объявляется с помощью управляющего слова "my".

    ReplyDelete
  7. $x не может не локализоваться в цикле. Перл локализует переменную цикла всегда. Даже если её объявить как локальную лексическую. Причем под словом "локализует" я подразумеваю операцию "local". И perl делает это не смотря на то, что нельзя динамически локализовать локальную_лексическую переменную. Если Вы попробуете это сделать самостоятельно с любой переменной, получите ошибку. А от того, будете ли вы лексически локализовывать переменную цикла (for my $bla (@list)) или нет (for $bla (@list)) ничего не изменится. PERL сам её динамически локализует.

    ReplyDelete
  8. Можно, только 'use strict' это не понравится.

    ReplyDelete
  9. Мне казалось, что примером я как раз и хотел показать почему "Перл локализует переменную цикла всегда" (для цикла foreach).
    "если $x не локализуется в цикле" следует читать как "если бы $x не локализовалась в цикле"

    ReplyDelete
  10. Возможно я Вас не правильно понял. На тему дискуссии написал небольшое резюме. http://intervoice.livejournal.com/.

    ReplyDelete
  11. хы :)
    первый код - это действительно блок foreach и for в таких случаях не стоит писать. Дело в том что даже если ты не написал my (for my $x (2..3){}), то perl сделает это за тебя - т.е. в блоке for теперь видится своя переменная $x, а не внешняя (та что за пределами блока), а вторая конструкция ("сишная") - дык она и в перле также работает :) что логично

    ReplyDelete
  12. Re: хы :)
    > первый код - это действительно блок foreach и for в таких случаях не стоит писать.
    The "foreach" keyword is actually a synonym for the "for" keyword, so you can use "foreach" for readability or "for" for brevity.

    ReplyDelete