Есть ли решение Perl для ленивых списков этой стороны Perl 6?

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

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

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

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

Ответ 1

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

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

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

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, который обеспечивает гораздо более богатый и полный интерфейс для ленивых массивов. Я сам не использовал модуль, поэтому ваш пробег может отличаться.

Все самое лучшее,

Пол

Ответ 2

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

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

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

... напишите так:

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

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

Ответ 3

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

Удачи,:)

Ответ 4

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

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

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

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

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

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

Ответ 5

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

use List::Gen;

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

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

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

Ответ 7

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

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

Ответ 8

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

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

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

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