Menu

Generatori in PHP: yield, yield from e uso della memoria

Un generatore è una funzione che usa yield per restituire valori uno alla volta, così foreach può scorrere un milione di elementi senza costruire un array. Impara yield, le chiavi, il risparmio di memoria, le sequenze infinite, yield from, send() e getReturn().

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

Un generatore è una funzione che contiene yield. Chiamarla non esegue il corpo; restituisce un oggetto Generator che foreach può scorrere. Ogni yield $value passa un valore al ciclo e mette in pausa la funzione finché il ciclo non chiede il successivo, quindi i valori vengono prodotti uno alla volta invece di essere prima raccolti in un array.

L'output si alterna tra il generatore e il ciclo: ogni valore viene creato solo quando il ciclo lo chiede.

I generatori risparmiano memoria

range(1, 1000000) costruisce un array di un milione di interi prima che il ciclo parta. Un generatore che produce gli stessi numeri non ne tiene mai più di uno. Eseguilo e confronta la memoria che serve a ciascuna versione:

Entrambi i cicli stampano la stessa somma, 500000500000, ma la versione con l'array riporta extra memory 16.0 MB e il generatore extra memory 0.0 MB. Il divario cresce con i dati. È la stessa idea che rende utili i generatori per file grandi, risultati di database e pagine di API: elabora un elemento, lascialo andare, prendi il successivo.

Chiavi e valori con yield

yield $key => $value imposta la chiave che riceve foreach ($gen as $key => $value). Senza chiave, un generatore numera i suoi valori da 0, come una lista. Un uso comune è leggere un file riga per riga con i numeri di riga:

In memoria c'è una sola riga alla volta, quindi la stessa funzione gestisce un file di log da 2 GB. fopen() e fgets() trattano le funzioni sui file usate qui.

Sequenze infinite e pipeline pigre

Un generatore può ciclare all'infinito, perché va avanti solo quanto chiede chi lo consuma. Fermati con break, oppure racchiudilo in un altro generatore che prende i primi valori. Concatenare piccoli generatori dà una pipeline in cui ogni passo è pigro:

fibonacci() non termina mai da sola, eppure il programma finisce: take() smette di chiedere dopo il numero richiesto, e nulla oltre viene mai calcolato.

yield from: delegare a un altro generatore

yield from inoltra ogni valore di un array, di un iteratore o di un altro generatore. È così che dividi un generatore in generatori più piccoli, o percorri ricorsivamente una struttura annidata:

yield from mantiene le chiavi interne, quindi entrambi gli array producono le chiavi 0 e 1. iterator_to_array() sovrascrive allora a e b. Passa false come secondo argomento ogni volta che contano solo i valori.

send() e getReturn()

Un generatore può anche ricevere valori. $x = yield; si mette in pausa e aspetta; $gen->send($value) riprende il generatore con $value come risultato di quello yield. E un generatore può fare return di un valore finale, che leggi con getReturn() quando ha finito.

La maggior parte del codice non ha mai bisogno di send(); leggere in modo pigro con foreach è l'uso di tutti i giorni. È il mattone alla base delle librerie di coroutine.

Un generatore si può scorrere una volta sola

I generatori vanno in avanti e non si possono riavvolgere. Una volta che un foreach ne ha consumato uno, un secondo ciclo sullo stesso oggetto lancia un'eccezione:

La soluzione è chiamare di nuovo la funzione generatrice per ogni passaggio, foreach (colors() as $c), oppure raccogliere i valori una volta con iterator_to_array(colors(), false) quando ti servono davvero due volte. Se ti ritrovi a farlo spesso, per quei dati la scelta giusta era un semplice array.

Domande frequenti

Cosa fa yield in PHP?

yield $value passa un valore al codice che scorre la funzione e mette in pausa la funzione in quel punto. L'iterazione successiva del ciclo la riprende dopo lo yield. Ogni funzione che contiene yield restituisce un oggetto Generator invece di essere eseguita quando la chiami.

Perché usare un generatore invece di un array?

Un array tiene in memoria tutti gli elementi insieme; un generatore produce un elemento alla volta. Scorrere range(1, 1000000) alloca tutti i milioni di interi, mentre un generatore che li produce con yield mantiene la memoria costante. I generatori permettono anche sequenze infinite e smettono di lavorare appena il ciclo si interrompe.

Cos'è yield from in PHP?

yield from $source inoltra ogni valore di un altro generatore, di un array o di un Traversable, come se il generatore corrente li avesse prodotti lui stesso. Vale anche come valore restituito dal generatore interno.

Posso scorrere un generatore due volte?

No. Un generatore va solo in avanti. Dopo che un foreach è terminato, scorrerlo di nuovo lancia Exception: Cannot traverse an already closed generator. Chiama di nuovo la funzione generatrice per ottenere un nuovo generatore, oppure salva i valori con iterator_to_array() se ti servono due volte.

Come converto un generatore in un array?

iterator_to_array($gen) raccoglie tutti i valori, mantenendo le chiavi del generatore. Se le chiavi si ripetono, per esempio dopo yield from, i valori successivi sovrascrivono i precedenti, quindi passa false come secondo argomento per ottenere una lista semplice: iterator_to_array($gen, false).

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA