GeneratorsResumen sobre los generadores
Los generadores proporcionan una manera sencilla de implementar
iteradores
sin el costo ni la complejidad de desarrollar una clase que implemente
la interfaz Iterator.
Un generador ofrece un medio conveniente para proporcionar datos a las bucles &foreach; sin
tener que construir un array en memoria de antemano, lo cual podría llevar al programa
a exceder un límite de memoria o requerir un tiempo de procesamiento considerable para generarlos.
En su lugar, se puede utilizar una función generadora,
que es idéntica a una
función normal,
excepto que en lugar de devolver
una sola vez, un generador puede utilizar &yield; tantas veces como sea necesario, para
proporcionar los valores a recorrer.
Al igual que con los iteradores, el acceso aleatorio a los datos no es posible.
Un ejemplo sencillo de este mecanismo es la reimplementación
de la función range en forma de generador.
La función estándar range debe generar un array
que contenga cada valor y devolverlo, lo cual puede llevar
a arrays de gran tamaño: por ejemplo, la llamada al código
range(0, 1000000) puede consumir significativamente más de
100 MB de memoria.
Como alternativa, se puede implementar un generador
xrange(), que solo necesitará memoria para la creación de un objeto Iterator, y deberá mantener internamente el estado actual del generador, lo cual resulta en un consumo de memoria inferior a 1 KB.
Implementación de la función range en forma de generador
= 0) {
throw new LogicException('El paso debe ser negativo');
}
for ($i = $start; $i >= $limit; $i += $step) {
yield $i;
}
}
}
/*
* Es de notar que las funciones range() y xrange() producen el
* mismo resultado, a continuación.
*/
echo 'Números impares de un solo dígito desde range(): ';
foreach (range(1, 9, 2) as $number) {
echo "$number ";
}
echo "\n";
echo 'Números impares de un solo dígito desde xrange(): ';
foreach (xrange(1, 9, 2) as $number) {
echo "$number ";
}
]]>
&example.outputs;
Los objetos Generator
Cuando se llama a una función generadora,
se devuelve un objeto de la clase interna Generator. Este objeto implementa la interfaz Iterator
de la misma manera que lo haría un objeto iterador que solo avanza, y proporciona los métodos que pueden ser llamados para manipular el estado
del generador, incluyendo el envío de valores y sus retornos.
Sintaxis de un Generador
Una función generadora se asemeja a una función normal, excepto que en lugar de
devolver un valor, un generador &yield; devuelve tantos valores como sea necesario.
Todas las funciones que contienen &yield; son funciones generadoras.
Cuando se llama a una función generadora, devuelve un objeto
que se puede recorrer. Cuando se recorre este objeto (por ejemplo, a través de una
bucle &foreach;), PHP llamará a los métodos de iteración del objeto cada
vez que necesite un valor, luego guardará el estado del generador
cuando genere un valor, para que pueda ser reanudado cuando
se requiera el siguiente valor.
Cuando no haya más valores para proporcionar, la función generadora puede simplemente
devolver, y el código de llamada continuará como si un array no tuviera más valores.
Un generador puede devolver valores,
que pueden ser recuperados utilizando Generator::getReturn.
La palabra clave yield
La palabra clave yield es el núcleo de una función generadora.
En su forma más simple, una instrucción yield se asemeja a una instrucción
return, excepto que en lugar de detener la ejecución de la función
y devolver, yield proporciona un valor al código que recorre el generador,
y pausa la ejecución de la función generadora.
Un ejemplo sencillo de producción de valores
&example.outputs;
Internamente, se asociarán claves enteras secuenciales
con los valores producidos, de la misma manera que para un
array no asociativo.
Provisión de valores con claves
PHP también soporta arrays asociativos, y los generadores
no son diferentes. Además de proporcionar valores simples, como hemos visto anteriormente, también se pueden
proporcionar claves simultáneamente.
La sintaxis para producir un par clave/valor es similar a la utilizada
para definir un array asociativo; así:
Producción de un par clave/valor
$fields;
}
}
foreach (input_parser($input) as $id => $fields) {
echo "$id:\n";
echo " $fields[0]\n";
echo " $fields[1]\n";
}
]]>
&example.outputs;
Producción de valores nulos
Yield puede ser llamado sin argumento para proporcionar un valor &null;
con una clave automática.
Producción de valores &null;
&example.outputs;
NULL
[1]=>
NULL
[2]=>
NULL
}
]]>
Producción de valores por referencia
Las funciones generadoras pueden producir valores por referencia.
Esto se hace de la misma manera que el
retorno por referencia
desde funciones : añadiendo un ET comercial (&) al nombre
de la función.
Producción de valores por referencia
0) {
yield $value;
}
}
/*
* Note que es posible cambiar $number en el bucle,
* y, dado que el generador proporciona referencias, $value
* en gen_reference() también cambia.
*/
foreach (gen_reference() as &$number) {
echo (--$number).'... ';
}
]]>
&example.outputs;
Delegación del generador vía yield from
La delegación del generador permite obtener los valores de otro generador, de un objeto Traversable, o
de un array utilizando la palabra clave yield from. El generador externo obtendrá así todos los valores del generador
interno, del objeto, o del array mientras no sea inválido, después de lo cual, la ejecución continuará en el generador externo.
Si un generador se utiliza con la expresión yield from,
la expresión yield from también devolverá cualquier valor
devuelto por el generador interno.
Almacenamiento en un array (e.g. con iterator_to_array)yield from no reinicia las
claves. Preserva las claves devueltas por el objeto
Traversable, o array.
Por lo tanto, algunos valores pueden compartir una clave común con otros yield o
yield from, que, al insertarse
en un array, sobrescribirá los valores anteriores con esa clave.
Un caso frecuente en el que esto es importante es iterator_to_array
devolviendo un array con clave por defecto, lo que puede llevar a
resultados potencialmente inesperados.
iterator_to_array tiene un segundo parámetro
preserve_keys que puede ser definido en &false;
para recolectar todos los valores ignorando las claves
devueltas por el Generator.
yield from con iterator_to_array
&example.outputs;
int(1)
[1]=>
int(4)
[2]=>
int(3)
}
]]>
Uso básico de yield from
&example.outputs;
yield from y los valores devueltos
getReturn();
]]>
&example.outputs;
Comparación de los generadores con los objetos Iterator
La principal ventaja de los generadores es su simplicidad. Menos código
debe ser escrito que cuando se trata de implementar una clase
Iterator, y generalmente es más legible.
Por ejemplo, la función y la clase siguientes son equivalentes:
fileHandle = fopen($fileName, 'r')) {
throw new RuntimeException('Imposible abrir el fichero: "' . $fileName . '"');
}
}
public function rewind() {
fseek($this->fileHandle, 0);
$this->line = fgets($this->fileHandle);
$this->i = 0;
}
public function valid() {
return false !== $this->line;
}
public function current() {
return $this->line;
}
public function key() {
return $this->i;
}
public function next() {
if (false !== $this->line) {
$this->line = fgets($this->fileHandle);
$this->i++;
}
}
public function __destruct() {
fclose($this->fileHandle);
}
}
]]>
Sin embargo, esta flexibilidad tiene un costo: los generadores son iteradores
que solo avanzan, y no pueden ser reinicializados una vez
que su recorrido haya comenzado. Esto también significa que el mismo generador no puede
ser utilizado varias veces: el generador deberá ser reconstruido
llamando nuevamente a la función generadora.
&reftitle.seealso;
Iteración de Objeto