Historia pytania:
Pomysł leniwych list (lazy lists) jest stosowany w wielu językach programowania do przetwarzania potencjalnie nieskończonych sekwencji lub opóźnionych obliczeń. W Perl brakuje wbudowanego wsparcia dla generatorów, jak na przykład yield w Pythonie, jednak koncepcja leniwych struktur danych może być realizowana za pomocą zamknięć, iteratorów oraz specjalnych modułów (na przykład, Iterator::Simple).
Problem:
Główną trudnością jest poprawna organizacja przekazywania stanu między wywołaniami funkcji / zamknięcia oraz zwalnianie pamięci. Ponowne użycie zmiennych, niedostępność danych, opóźnione lub zbyt wczesne obliczenia często prowadzą do błędów lub wycieków pamięci.
Rozwiązanie:
Należy używać anonimowych podprogramów (closures), które inkapsulują wewnętrzny stan. Takie podejście pozwala na realizację generatorów na żądanie. Można skorzystać z zewnętrznych modułów, takich jak Iterator::Simple lub samodzielnie napisać leniwy generator.
Przykład kodu:
my $counter = lazy_counter(5); while (my $v = $counter->()) { print "$v "; } sub lazy_counter { my $max = shift; my $current = 1; return sub { return undef if $current > $max; return $current++; }; }
Kluczowe cechy:
Jak bezpieczne jest przechowywanie wewnętrznego stanu iteratora w zagnieżdżonej zmiennej leksykalnej? Jak wpływa to na zarządzanie pamięcią?
Wewnętrzny stan zamknięcia nie jest zwalniany tak długo, jak istnieje odniesienie do closure. Jeśli zamknięcie przypadkowo zawiera duże tablice lub odniesienia do zewnętrznych struktur, prowadzi to do wycieków pamięci.
Czy można przekazywać kontrolę między kilkoma leniwymi listami lub generatorami bezpośrednio, jak w językach wspierających yield?
W Perl nie da się przeprowadzić pełnej wymiany kontroli (coroutine-like) jak w przypadku yield, ponieważ podprogram nie "zamraża się". Każdy generator jest ściśle kontrolowany przez swoje zamknięcie i stos wywołań. W bardziej złożonych scenariuszach warto używać modułów typu Coro lub AnyEvent.
Czym różni się realizacja iteratora za pomocą zamknięcia od zwykłej pętli z zachowaniem pozycji w zmiennej zewnętrznej?
Zamknięcie zapewnia inkapsulację stanu i zapobiega przypadkowemu zmienianiu go z zewnątrz. Używając zewnętrznego wskaźnika, możliwe równoległe użycie może być niemożliwe lub prowadzić do błędów synchronizacji.
Inżynier pisze samodzielnego iteratora przez zmienną globalną, zapominając o szczegółach scoping. W kilku częściach programu używany jest ten sam licznik, który "ucieka naprzód" i łamie logikę iteracji.
Zalety:
Wady:
Używane jest zamknięcie, które inkapsuluje stan. Generator można przekazywać do dowolnych części programu, uruchamiać jednocześnie wiele instancji.
Zalety:
Wady: