mirror of
https://github.com/php/doc-es.git
synced 2026-03-25 07:52:21 +01:00
git-svn-id: https://svn.php.net/repository/phpdoc/es/trunk@337896 c90b9560-bf6c-de11-be94-00142212c4b1
597 lines
15 KiB
XML
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 &) 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
|
|
-->
|