Стоит ли использовать каждую функцию Perl?

Из perldoc -f each мы читаем:

Для каждого хэша существует один итератор, который используется всеми вызовами функций each, keys и values в программе; его можно сбросить, прочитав все элементы из хеша или оценив keys HASH или values HASH.

Итератор не сбрасывается при выходе из области видимости, содержащей each(), и это может привести к ошибкам:

my %h = map { $_, 1 } qw(1 2 3);
while (my $k = each %h) { print "1: $k\n"; last }
while (my $k = each %h) { print "2: $k\n"       }

Выход:

1: 1
2: 3
2: 2

Каковы общие обходные пути для такого поведения? И стоит ли вообще использовать each?


person Eugene Yarmash    schedule 07.03.2010    source источник
comment
Я полагаю, что распространенные обходные пути включают оценку keys HASH или values HASH   -  person Daniel LeCheminant    schedule 07.03.2010


Ответы (8)


Я думаю, что это стоит использовать, пока вы знаете об этом. Это идеально, когда вам нужны и ключ, и значение в итерации:

while (my ($k,$v) = each %h) {
    say "$k = $v";
}

В вашем примере вы можете сбросить итератор, добавив keys %h; вот так:

my %h = map { $_ => 1 } qw/1 2 3/;
while (my $k = each %h) { print "1: $k\n"; last }
keys %h;  # reset %h
while (my $k = each %h) { print "2: $k\n" }

Начиная с Perl 5.12, each также позволяет итерация по массиву.

person draegtun    schedule 07.03.2010

Я считаю, что each очень удобен для таких идиом:

my $hashref = some_really_complicated_method_that_builds_a_large_and_deep_structure();
while (my ($key, $value) = each %$hashref)
{
    # code that does stuff with both $key and $value
}

Сравните этот код с этим:

my $hashref = ...same call as above
foreach my $key (keys %$hashref)
{
    my $value = $hashref->{$key};
    # more code here...
}

В первом случае и $key, и $value сразу доступны для тела цикла. Во втором случае сначала нужно получить $value. Кроме того, список ключей $hashref может быть очень большим, что занимает много памяти. Это иногда проблема. each не несет таких накладных расходов.

Однако недостатки each не сразу бросаются в глаза: при преждевременном выходе из цикла итератор хеша не сбрасывается. Кроме того (и я нахожу это более серьезным и даже менее заметным): вы не можете вызывать keys(), values() или другой each() из этого цикла. Это приведет к сбросу итератора, и вы потеряете свое место в цикле while. Цикл while будет продолжаться вечно, что, безусловно, является серьезной ошибкой.

person Ether    schedule 07.03.2010

each слишком опасен для использования, и многие руководства по стилю полностью запрещают его использование. Опасность заключается в том, что если цикл each будет прерван до конца хэша, то с него начнется следующий цикл. Это может вызвать очень трудно воспроизводимые ошибки; поведение одной части программы будет зависеть от совершенно не связанной с ней другой части программы. Вы можете использовать each правильно, но как насчет каждого когда-либо написанного модуля, который может использовать ваш хэш (или хэш-ссылку; это то же самое)?

keys и values всегда безопасны, так что просто используйте их. keys в любом случае упрощает обход хеша в детерминированном порядке, что почти всегда более полезно. (for my $key (sort keys %hash) { ... })

person jrockway    schedule 08.03.2010
comment
используете много глобальных хэшей, не так ли? - person ysth; 09.03.2010
comment
Неважно, глобальный он или нет. Даже частные атрибуты класса уязвимы для этой проблемы. Затрагивается все, что возвращает ссылку на хэш. - person jrockway; 09.03.2010

каждый из них не только стоит использовать, но и является обязательным, если вы хотите перебрать весь связанный хэш, слишком большой для памяти.

Ключи void-context() (или значения, но согласованность хороша) перед началом цикла является единственным необходимым «обходным решением»; есть ли причина, по которой вы ищете какой-то другой обходной путь?

person ysth    schedule 08.03.2010
comment
Отличный момент! Это лучшая (единственная?) причина для использования each, которую я могу придумать. - person daotoad; 08.03.2010

используйте функцию keys() для сброса итератора. См. faq для получения дополнительной информации

person ghostdog74    schedule 07.03.2010

each имеет встроенную скрытую глобальную переменную, которая может вам навредить. Если вам не нужно такое поведение, безопаснее просто использовать keys.

Рассмотрим этот пример, где мы хотим сгруппировать наши пары k/v (да, я знаю, что printf сделает это лучше):

#!perl

use strict;
use warnings;

use Test::More 'no_plan';

{   my %foo = map { ($_) x 2 } (1..15);

    is( one( \%foo ), one( \%foo ), 'Calling one twice works with 15 keys' );
    is( two( \%foo ), two( \%foo ), 'Calling two twice works with 15 keys' );
}

{   my %foo = map { ($_) x 2 } (1..105);

    is( one( \%foo ), one( \%foo ), 'Calling one twice works with 105 keys' );
    is( two( \%foo ), two( \%foo ), 'Calling two twice works with 105 keys' );
}


sub one {
    my $foo = shift;

    my $r = '';

    for( 1..9 ) {
        last unless my ($k, $v) = each %$foo;

        $r .= "  $_: $k -> $v\n";
    }
    for( 10..99 ) {
        last unless my ($k, $v) = each %$foo;

        $r .= " $_: $k -> $v\n";
    }

    return $r;
}

sub two {
    my $foo = shift;

    my $r = '';

    my @k = keys %$foo;

    for( 1..9 ) {
        last unless @k;
        my $k = shift @k;

        $r .= "  $_: $k -> $foo->{$k}\n";
    }
    for( 10..99 ) {
        last unless @k;
        my $k = shift @k;

        $r .= "  $_: $k -> $foo->{$k}\n";
    }

    return $r;
}

Отладка ошибки, показанной в приведенных выше тестах, в реальном приложении была бы ужасно болезненной. (Для лучшего вывода используйте Test::Differences eq_or_diff вместо is.)

Конечно, one() можно исправить, используя keys для очистки итератора в начале и в конце подпрограммы. Если ты помнишь. Если все ваши коллеги помнят. Это совершенно безопасно, пока никто не забудет.

Не знаю, как вы, а я просто буду использовать keys и values.

person daotoad    schedule 08.03.2010

Лучше всего использовать его как название: each. Вероятно, это неправильно, если вы имеете в виду «дайте мне первую пару ключ-значение» или «дайте мне первые две пары» или что-то еще. Просто имейте в виду, что идея достаточно гибкая, так что каждый раз, когда вы ее вызываете, вы получаете следующую пару (или ключ в скалярном контексте).

person Axeman    schedule 08.03.2010

each() может быть более эффективным, если вы перебираете связанный хэш, например базу данных, содержащую миллионы ключей; таким образом вам не нужно загружать все ключи в память.

person Walter H    schedule 08.03.2010