Storia della questione:
L'idea delle liste pigre (lazy lists) è applicata in molti linguaggi di programmazione per elaborare sequenze potenzialmente infinite o calcoli rinviati. In Perl non esiste un supporto integrato per i generatori, come ad esempio yield in Python, tuttavia il concetto di strutture dati pigre è realizzabile attraverso chiusure, iteratori e moduli speciali (ad esempio, Iterator::Simple).
Problema:
La principale difficoltà è organizzare correttamente il passaggio di stato tra le chiamate di funzione/chiusura e liberare la memoria. La riutilizzazione delle variabili, l'inaccessibilità dei dati, i calcoli rinviati o troppo anticipati portano spesso a errori o perdite di memoria.
Soluzione:
Utilizzare sottoprogrammi anonimi (closures) che incapsulano lo stato interno. Questo approccio consente di implementare generatori su richiesta. Si possono utilizzare moduli di terze parti, ad esempio, Iterator::Simple o scrivere autonomamente un generatore pigro.
Esempio di codice:
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++; }; }
Caratteristiche chiave:
Quanto è sicuro memorizzare lo stato interno dell'iteratore in una variabile lessicale annidata? Come influisce sulla gestione della memoria?
Lo stato interno della chiusura non viene liberato finché esiste un riferimento alla chiusura stessa. Se la chiusura contiene accidentalmente array di grandi dimensioni o riferimenti a strutture esterne, ciò porterà a perdite di memoria.
È possibile trasferire il controllo tra più liste pigre o generatori direttamente come nei linguaggi con supporto per yield?
In Perl non è possibile effettuare un vero e proprio trasferimento di controllo (simile a coroutine) come con yield, perché la sottoprogramma non viene "congelata". Ogni generatore è rigorosamente controllato dalla propria chiusura e dal proprio stack delle chiamate. Per scenari complessi è opportuno utilizzare moduli come Coro o AnyEvent.
Qual è la differenza tra l'implementazione di un iteratore tramite chiusura e tramite un ciclo normale con memorizzazione della posizione in una variabile esterna?
La chiusura assicura l'incapsulamento dello stato e previene modifiche accidentali dall'esterno. Se si utilizza un puntatore esterno, l'uso parallelo può essere impossibile o portare a errori di sincronizzazione.
Un ingegnere scrive un iteratore fatto a mano tramite una variabile globale, dimenticando le peculiarità dello scoping. Nelle diverse parti del programma viene utilizzato lo stesso contatore, che "corre avanti" e rompe la logica di iterazione.
Pro:
Contro:
Si utilizza una chiusura che incapsula lo stato. Il generatore può essere passato a qualsiasi parte del programma ed eseguire più istanze simultaneamente.
Pro:
Contro: