programowanieProgramista Perl (backend/data)

Jak są zaimplementowane i działają iteratory i generatory w Perl? Jakie powszechne wzorce leniwego obliczania są stosowane i jakie są ich ograniczenia?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź

W Perl nie ma natywnych generatorów, jak w Pythonie, ale można zaimplementować leniwe obliczenia i iteratory za pomocą zamknięć, śledzenia stanu w zmiennych leksykalnych oraz funkcji-generatorów:

sub counter { my $x = shift; return sub { return $x++; }; } my $it = counter(5); print $it->(), ", ", $it->(); # 5, 6

Dla złożonych iteratorów często używa się modułów CPAN (Iterator::Simple, List::Gen). Klasyczny leniwy wzorzec — zwracanie anonimnej podprogramy z zapisanym stanem.

Minusy: brak wbudowanego wsparcia dla yield, wielu modułom CPAN brakuje kompozycyjności. Rekurencja również ogranicza się do rozmiaru stosu.

Pytanie z podstępem

Czy można zaimplementować nieskończoną leniwą listę liczb Fibonacciego w Perl bez przepełnienia pamięci?

Odpowiedź: Tak, za pomocą zamknięcia:

sub fibonacci { my ($a, $b) = (0, 1); return sub { ($a, $b) = ($b, $a+$b); return $a; }; } my $fib = fibonacci(); print $fib->(), ", ", $fib->(), ", ", $fib->();

Ale jeśli umieścisz wyniki w tablicy, z czasem przepełni pamięć (tzn. naprawdę „leniwy” jest tylko sam generator).

Przykłady rzeczywistych błędów z powodu braku znajomości niuansów tematu


Historia

Na projekcie zaimplementowano własny iterator do przeszukiwania ogromnego pliku, zaimplementowany przez tablicę wewnątrz obiektu. Iterator ładował cały plik do pamięci — i przy wzroście pliku serwis zaczął wywoływać OOM przy pracy z wieloma instancjami.


Historia

Zamknięcie-generator dla sekwencji elementów do raportu prowadziło do nieoczekiwanej przecieku pamięci — wewnątrz zamknięcia przypadkowo utrzymywana była referencja do dużej tablicy danych wejściowych, co uniemożliwiało odśmiecanie przez zbieracza.


Historia

Próba zaimplementowania złożonego generatora za pomocą rekurencji bez śledzenia głębokości doprowadziła do przekroczenia limitu stosu przy przetwarzaniu naprawdę dużych danych, zamiast oczekiwanego „leniwego” przeszukiwania.