1
0
mirror of https://github.com/php/doc-es.git synced 2026-03-25 07:52:21 +01:00
Files
archived-doc-es/language/generators.xml
Pedro Antonio Gil Rodríguez 2dc2e6a7af Actualización a la última versión
git-svn-id: https://svn.php.net/repository/phpdoc/es/trunk@337896 c90b9560-bf6c-de11-be94-00142212c4b1
2015-09-22 15:24:11 +00:00

597 lines
15 KiB
XML

<?xml version="1.0" encoding="utf-8"?>
<!-- $Revision$ -->
<!-- EN-Revision: e7c3d74e3369e87962da229da463c372474e4df2 Maintainer: seros Status: ready -->
<!-- Reviewed: no Maintainer: andresdzphp -->
<chapter xml:id="language.generators" xmlns="http://docbook.org/ns/docbook">
<title>Generadores</title>
<sect1 xml:id="language.generators.overview">
<title>Información general de los generadores</title>
<?phpdoc print-version-for="generators"?>
<para>
Los generadores proporcionan un modo fácil de implementar
<link linkend="language.oop5.iterations">iteradores</link> simples sin la
sobrecarga o complejidad de implementar una clase que implemente la
interfaz <classname>Iterator</classname>.
</para>
<para>
Un generador permite escribir código que utilice &foreach; para iterar sobre un
conjunto de datos sin que sea necesario cargar el array en memoria, lo que puede ocasionar
que se exceda el límite de memoria, o requiera una cantidad considerable de
tiempo de procesado para generarse. En su lugar, se puede escribir una función generadora,
que es igual que una
<link linkend="functions.user-defined">función</link> normal, con la salvedad de que en vez
de
hacer un solo <link linkend="functions.returning-values">return</link>, un
generador puede invocar &yield; tantas veces como necesite para proporcionar
valores por los que iterar.
</para>
<para>
Un ejemplo simple de esto es reimplementar la función <function>range</function>
como un generador. La función estándar <function>range</function>
tiene que generar un array con cada uno de los valores y devolverlo, lo que puede
resultar en arrays grandes: por ejemplo, llamar
<command>range(0, 1000000)</command> resultará en más de 100 MB de
memoria utilizada.
</para>
<para>
Como alternativa, se puede implementar un generador <literal>xrange()</literal>,
que sólo necesitará memoria para crear un
objeto <classname>Iterator</classname> y controlar el estado actual del
generador de manera interna, lo que no ocupa más de 1 kilobyte.
</para>
<example>
<title>Implementando <function>range</function> como generador</title>
<programlisting role="php">
<![CDATA[
<?php
function xrange($start, $limit, $step = 1) {
if ($start < $limit) {
if ($step <= 0) {
throw new LogicException('Step tiene que ser +ve');
}
for ($i = $start; $i <= $limit; $i += $step) {
yield $i;
}
} else {
if ($step >= 0) {
throw new LogicException('Step tiene que ser -ve');
}
for ($i = $start; $i >= $limit; $i += $step) {
yield $i;
}
}
}
/*
* Obsereve que tanto range() como xrange() producen la misma
* salida a continuación.
*/
echo 'Números impares de una cifra de range(): ';
foreach (range(1, 9, 2) as $number) {
echo "$number ";
}
echo "\n";
echo 'Números impares de una cifra de xrange(): ';
foreach (xrange(1, 9, 2) as $number) {
echo "$number ";
}
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Números impares de una cifra de range(): 1 3 5 7 9
Números impares de una cifra de xrange(): 1 3 5 7 9
]]>
</screen>
</example>
<sect2 xml:id="language.generators.object">
<title>Objetos <classname>Generator</classname></title>
<para>
Cuando una función generadora es invocada por primera vez, se devuelve un
objeto de la clase interna <classname>Generator</classname>. Este objeto
implementa la interfaz <classname>Iterator</classname> de la misma forma
que lo haría un objeto iterador de solo avance, y proporciona métodos que pueden
ser invocados para manipular el estado del generador, incluyendo el envío
de valores hacia y la devolución de valores desde él.
</para>
</sect2>
</sect1>
<sect1 xml:id="language.generators.syntax">
<title>Generator syntax</title>
<para>
Una función generadora es igual que una función normal, con la diferencia de que en vez
de devolver un valor, un generador invoca &yield; tantas veces como necesita.
</para>
<para>
Cuando se llama a una función generadora, devuelve un objeto que puede ser
iterado. Cuando se itera sobre ese objeto (por ejemplo, con un
bucle &foreach;), PHP llamará a la función generadora cada vez que necesite un
valor, y guardará el estado del generador cuando este provea un
valor con yield para que ese estado pueda ser recuperado cuando el próximo valor sea requerido.
</para>
<para>
Cuando no hay más valores que se puedan proporcionar, la función generadora
puede simplemente terminar, y el código desde el que se la llama continuará como si un array se hubiera quedado
sin valores.
</para>
<note>
<para>
Un generador no puede retornar un valor: hacerlo resultaría en un error de
compilación. Un <command>return</command> vacío es válido en cuanto a sintaxis dentro
de un generador y terminará el generador.
</para>
</note>
<sect2 xml:id="control-structures.yield">
<title><command>yield</command> keyword</title>
<para>
La clave de una función generadora es la palabra reservada <command>yield</command>.
En su forma más simple, la sentencia yield es parecida a la sentencia
return, excepto en que en vez de detener la ejecución de la función y
devolver un valor, yield facilita el valor al bucle que itera sobre el
generador y pausa la ejecución de la función generadora.
</para>
<example>
<title>Ejemplo sencillo de facilitar valores con yield</title>
<programlisting role="php">
<![CDATA[
<?php
function gen_one_to_three() {
for ($i = 1; $i <= 3; $i++) {
// Observe que $i es preservado entre yields
yield $i;
}
}
$generator = gen_one_to_three();
foreach ($generator as $value) {
echo "$value\n";
}
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
1
2
3
]]>
</screen>
</example>
<note>
<para>
Internamente, las claves enteras secuenciales serán asociadas con los valores
sobre los que se usa yield, como un array no asociativo.
</para>
</note>
<caution>
<para>
Si se utiliza yield en el contexto de una expresión (por ejemplo, en el lado derecho
de una asignación), se debe poner la sentencia yield entre
paréntesis en PHP 5. Por ejemplo, esto es válido:
</para>
<informalexample>
<programlisting role="php">
<![CDATA[
$data = (yield $value);
]]>
</programlisting>
</informalexample>
<para>
Pero esto no lo es, y resultará en un error de análisis en PHP 5:
</para>
<informalexample>
<programlisting role="php">
<![CDATA[
$data = yield $value;
]]>
</programlisting>
</informalexample>
<para>
Las restricciones parentéticas no se aplican en PHP 7.
</para>
<para>
Esta sintaxis podría usarse junto con el método
<methodname>Generator::send</methodname>.
</para>
</caution>
<sect3 xml:id="control-structures.yield.associative">
<title>Utilizar yield para facilitar valores con claves</title>
<para>
PHP soporta arrays asociativos, y los generadores no son menos. Además
de facilitar valores simples, como se muestra arriba, también se puede facilitar
una clave al mismo tiempo.
</para>
<para>
La sintaxis para facilitar un par clave-valor es muy similar a la utilizada para
definir un array asociativo, como se muestra a continuación.
</para>
<example>
<title>Facilitar un par clave-valor</title>
<programlisting role="php">
<![CDATA[
<?php
/*
* La entrada son campos separados por punto y coma, con el primer
* campo siendo la ID utilizada como clave.
*/
$input = <<<'EOF'
1;PHP;Likes dollar signs
2;Python;Likes whitespace
3;Ruby;Likes blocks
EOF;
function input_parser($input) {
foreach (explode("\n", $input) as $line) {
$fields = explode(';', $line);
$id = array_shift($fields);
yield $id => $fields;
}
}
foreach (input_parser($input) as $id => $fields) {
echo "$id:\n";
echo " $fields[0]\n";
echo " $fields[1]\n";
}
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
1:
PHP
Likes dollar signs
2:
Python
Likes whitespace
3:
Ruby
Likes blocks
]]>
</screen>
</example>
<caution>
<para>
Como en el ejemplo anterior, facilitar un par clave-valor
en contexto de expresión requiere que la sentencia yield sea
puesta entre paréntesis:
</para>
<informalexample>
<programlisting role="php">
<![CDATA[
$data = (yield $key => $value);
]]>
</programlisting>
</informalexample>
</caution>
</sect3>
<sect3 xml:id="control-structures.yield.null">
<title>Facilitar valores nulos</title>
<para>
Yield puede ser invocado sin argumentos para facilitar un valor &null; con una
clave automática.
</para>
<example>
<title>Yielding &null;s</title>
<programlisting role="php">
<![CDATA[
<?php
function gen_three_nulls() {
foreach (range(1, 3) as $i) {
yield;
}
}
var_dump(iterator_to_array(gen_three_nulls()));
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
array(3) {
[0]=>
NULL
[1]=>
NULL
[2]=>
NULL
}
]]>
</screen>
</example>
</sect3>
<sect3 xml:id="control-structures.yield.references">
<title>Facilitar por referencia</title>
<para>
Las funciones generadoras son capaces de facilitar valores por referencia igual que lo hacen por
valor. Esto se hace de la misma forma que
<link linkend="functions.returning-values">devolviendo referencias desde funciones</link>:
poniendo un ampersand (signo &amp;) delante del nombre de la función.
</para>
<example>
<title>Facilitar valores por referencia</title>
<programlisting role="php">
<![CDATA[
<?php
function &gen_reference() {
$value = 3;
while ($value > 0) {
yield $value;
}
}
/*
* Observe que es posible cambiar $number desde dentro del bucle, y
* dado que el generador está facilitando referencias, $value
* dentro de gen_reference() cambia.
*/
foreach (gen_reference() as &$number) {
echo (--$number).'... ';
}
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
2... 1... 0...
]]>
</screen>
</example>
</sect3>
<sect3 xml:id="control-structures.yield.from">
<title>Delegación de generadores mediante <command>yield from</command></title>
<para>
En PHP 7, la delegación de generadores permite producir valores desde otro
generador, objeto <classname>Traversable</classname>, o
<type>array</type> mediante la palabra reservada <command>yield from</command>.
El generador externo producirá entonces todos los valores desde el generador interno,
object, o array hasta que este ya no sea válido, después de lo cual la ejecuión
continuará en el generador externo.
</para>
<para>
Si un generador se emplea con <command>yield from</command>, la
expresión <command>yield from</command> también devolverá cualquier valor
devuelto por el generador interno.
</para>
<example>
<title>Uso básico de <command>yield from</command></title>
<programlisting role="php">
<![CDATA[
<?php
function contar_hasta_diez() {
yield 1;
yield 2;
yield from [3, 4];
yield from new ArrayIterator([5, 6]);
yield from siete_ocho();
yield 9;
yield 10;
}
function siete_ocho() {
yield 7;
yield from eight();
}
function ocho() {
yield 8;
}
foreach (contar_hasta_diez() as $num) {
echo "$num ";
}
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
1 2 3 4 5 6 7 8 9 10
]]>
</screen>
</example>
<example>
<title><command>yield from</command> y valores devueltos</title>
<programlisting role="php">
<![CDATA[
<?php
function contar_hasta_diez() {
yield 1;
yield 2;
yield from [3, 4];
yield from new ArrayIterator([5, 6]);
yield from siete_ocho();
return yield from nueve_diez();
}
function siete_ocho() {
yield 7;
yield from ocho();
}
function ocho() {
yield 8;
}
function nueve_diez() {
yield 9;
return 10;
}
$gen = contar_hasta_diez();
foreach ($gen as $num) {
echo "$num ";
}
echo $gen->getReturn();
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
1 2 3 4 5 6 7 8 9 10
]]>
</screen>
</example>
</sect3>
</sect2>
</sect1>
<sect1 xml:id="language.generators.comparison">
<title>Comparación entre generadores y objetos <classname>Iterator</classname></title>
<para>
La principal ventaja de los generadores es su simplicadad. Se ha de escribir
mucho menos código repetitivo en comparación con el necesario para implementar una
clase <classname>Iterator</classname>, y el código es generalmente mucho más
legible. Por ejemplo, la siguiente función y clase son equivalentes:
</para>
<informalexample>
<programlisting role="php">
<![CDATA[
<?php
function getLinesFromFile($fileName) {
if (!$fileHandle = fopen($fileName, 'r')) {
return;
}
while (false !== $line = fgets($fileHandle)) {
yield $line;
}
fclose($fileHandle);
}
// Contra...
class LineIterator implements Iterator {
protected $fileHandle;
protected $line;
protected $i;
public function __construct($fileName) {
if (!$this->fileHandle = fopen($fileName, 'r')) {
throw new RuntimeException('Couldn\'t open file "' . $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);
}
}
?>
]]>
</programlisting>
</informalexample>
<para>
La flexibilidad, sin embargo, tiene un coste: los generadores son iteradores
unidireccionales, y no pueden ser rebobinados una vez la iteración ha empezado. Esto también
significa que se puede iterar sobre el mismo generador varias veces: el
generador necesitará ser o bien reconstruido llamando a la función generadora
de nuevo, o bien clonado a través de
la palabra clave <link linkend="language.oop5.cloning">clone</link>.
</para>
</sect1>
</chapter>
<!-- Keep this comment at the end of the file
Local variables:
mode: sgml
sgml-omittag:t
sgml-shorttag:t
sgml-minimize-attributes:nil
sgml-always-quote-attributes:t
sgml-indent-step:1
sgml-indent-data:t
indent-tabs-mode:nil
sgml-parent-document:nil
sgml-default-dtd-file:"~/.phpdoc/manual.ced"
sgml-exposed-tags:nil
sgml-local-catalogs:nil
sgml-local-ecat-files:nil
End:
vim600: syn=xml fen fdm=syntax fdl=2 si
vim: et tw=78 syn=sgml
vi: ts=1 sw=1
-->