Есть ли решение Perl для ленивых списков этой стороны Perl 6? - PullRequest
11 голосов
/ 21 сентября 2008

Кто-нибудь нашел хорошее решение для лениво оцененных списков в Perl? Я пробовал несколько способов превратить что-то вроде

for my $item ( map { ... } @list ) { 
}

в ленивую оценку - связывая @list, например. Я стараюсь не ломать и писать исходный фильтр для этого, потому что они мешают вашей способности отлаживать код. Кто-нибудь имел успех. Или вам просто нужно сломаться и использовать цикл while?

Примечание: Полагаю, я должен упомянуть, что иногда я цепляюсь за иногда длинные цепочки grep-map для функционального преобразования списков. Так что это не столько цикл foreach или цикл while. Это то, что выражения карты имеют тенденцию упаковывать больше функциональности в одно и то же вертикальное пространство.

Ответы [ 8 ]

13 голосов
/ 21 сентября 2008

Как упомянуто ранее, for (каждый) - активный цикл, поэтому он хочет оценить весь список перед запуском.

Для простоты я бы рекомендовал использовать объект итератора или замыкание, а не пытаться получить лениво вычисляемый массив. В то время как вы можете использовать привязку для лениво оцененного бесконечного списка, вы можете столкнуться с проблемами, если когда-нибудь попросите (прямо или косвенно, как в предыдущем разделе) весь список (или даже размер весь список).

Без написания полного класса или использования каких-либо модулей вы можете создать простую фабрику итераторов, просто используя замыкания:

sub make_iterator {
    my ($value, $max, $step) = @_;

    return sub {
        return if $value > $max;    # Return undef when we overflow max.

        my $current = $value;
        $value += $step;            # Increment value for next call.
        return $current;            # Return current iterator value.
    };
}

А затем использовать его:

# All the even numbers between 0 -  100.
my $evens = make_iterator(0, 100, 2);

while (defined( my $x = $evens->() ) ) {
    print "$x\n";
}

В CPAN также есть модуль Tie :: Array :: Lazy , который обеспечивает более богатый и полный интерфейс для ленивых массивов. Я не использовал модуль сам, поэтому ваш пробег может отличаться.

Всего наилучшего,

Пол

9 голосов
/ 21 сентября 2008

[Sidenote: Имейте в виду, что каждый отдельный шаг по цепочке карт / grep стремится. Если вы сразу дадите ему большой список, ваши проблемы начнутся гораздо раньше, чем в финале foreach.]

Что вы можете сделать, чтобы избежать полной перезаписи, это обернуть ваш цикл внешним циклом. Вместо того чтобы писать это:

for my $item ( map { ... } grep { ... } map { ... } @list ) { ... }

… напишите это так:

while ( my $input = calculcate_next_element() ) {
    for my $item ( map { ... } grep { ... } map { ... } $input ) { ... }
}

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

7 голосов
/ 21 сентября 2008

Если вы хотите создавать ленивые списки, вам придется написать свой собственный итератор. Если у вас есть это, вы можете использовать что-то вроде Object :: Iterate , у которого есть версии с поддержкой итераторов map и grep. Посмотрите на источник для этого модуля: он довольно прост, и вы узнаете, как написать свои собственные подпрограммы с поддержкой итераторов.

Удачи,:)

5 голосов
/ 21 сентября 2008

Существует по крайней мере один особый случай, когда for и foreach были оптимизированы, чтобы не генерировать весь список сразу. И это оператор дальности. Таким образом, у вас есть возможность сказать:

for my $i (0..$#list) {
  my $item = some_function($list[$i]);
  ...
}

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

Если вы хотите, чтобы ваш оператор map возвращал переменное число элементов, вы можете сделать это вместо этого:

for my $i (0..$#array) {
  for my $item (some_function($array[$i])) {
    ...
  }
}

Если вы хотите больше распространяющейся лени, чем эта, то лучше всего научиться использовать замыкания для создания отложенных списков. Отличная книга MJD Perl высшего порядка поможет вам пройти через эти техники. Однако имейте в виду, что они повлекут за собой гораздо большие изменения в вашем коде.

4 голосов
/ 26 января 2010

Вернем это из мертвых, чтобы упомянуть, что я только что написал модуль List::Gen для CPAN , который делает именно то, что искал плакат:

use List::Gen;

for my $item ( @{gen { ... } \@list} ) {...}

все вычисления списков являются ленивыми, и есть эквиваленты map / grep наряду с некоторыми другими функциями.

каждая из функций возвращает «генератор», который является ссылкой на связанный массив. вы можете использовать связанный массив напрямую или использовать несколько методов доступа, таких как итераторы.

3 голосов
/ 22 сентября 2008

Я задал похожий вопрос на perlmonks.org , и BrowserUk дал действительно хороший фреймворк в своем ответе . По сути, удобный способ получить ленивую оценку - создавать потоки для вычислений, по крайней мере, до тех пор, пока вы уверены, что хотите получить результаты, просто не сейчас. Если вы хотите, чтобы ленивая оценка не уменьшала задержки, а избегала вычислений, мой подход не поможет, поскольку он основан на модели push, а не модели pull. Возможно, используя Coro процедуры, вы также можете превратить этот подход в (однопоточную) модель натяжения.

Размышляя об этой проблеме, я также исследовал привязку массива к результатам потока, чтобы поток Perl-программ был похож на map, но пока мне нравится мой API, представляющий parallel "ключевое слово" ( замаскированный конструктор объекта), а затем вызов метода для результата. Более документированная версия кода будет опубликована в ответ на эту ветку и, возможно, также опубликована на CPAN.

3 голосов
/ 21 сентября 2008

Используйте итератор или рассмотрите возможность использования Tie :: LazyList из CPAN (который немного устарел).

2 голосов
/ 21 сентября 2008

Если я правильно помню, для / foreach все равно сначала получают весь список, так что лениво оцененный список будет прочитан полностью, а затем он начнет перебирать элементы. Поэтому я думаю, что нет другого пути, кроме как использовать цикл while. Но я могу ошибаться.

Преимущество цикла while заключается в том, что вы можете подделать ощущение лениво оцененного списка с помощью ссылки на код:

my $list = sub { return calculate_next_element };
while(defined(my $element = &$list)) {
    ...
}

В конце концов, я думаю, что галстук настолько близок, насколько это возможно в Perl 5.

...