Generátory v PHP
Není pochyb o tom, že se PHP s každou novou verzí posouvá dopředu. Některé vlastnosti si nese z minulosti a proto se jich nemůže tak rychle zbavit. Avšak tam, kde to hlasování a zpětná kompatibilita dovolí, tam se snaží držet krok s vyspělejší konkurencí. Minulý rok vydaná verze 5.5 přinesla mimo jiné jednu důležitou novinku: generátory. Obejdete se bez nich, ale o hodně se připravujete.
The old-fashioned way
Začnu jednoduchým příkladem bez použití generátoru:
<?php
function squares($from, $to)
{
$result = [];
for (; $from < $to; $from++) {
$result[] = $from * $from;
}
return $result;
}
foreach (squares(1, 10) as $value) {
echo $value;
}
// 1, 4, 9, 16, 25, 36, 49, 64, 81
Funkce přijme interval čísel a na každém z nich postupně vypočítá hodnotu jeho druhé mocniny. Na první pohled na této funkci není nic podezřelého, nic co by šlo napsat lépe. Pojďmě přidat nějakou podmínku, filtr. Jednoduše odebereme z výsledné množiny ta čísla, která jdou dělit třemi:
<?php
function removeDivisibleBy3($values)
{
$result = [];
foreach ($values as $value) {
if ($value % 3 != 0) {
$result[] = $value;
}
}
return $result;
}
foreach (removeDivisibleBy3(squares(1, 10)) as $value) {
echo $value;
}
// 1, 4, 16, 25, 49, 64
Na první pohled zase nic špatného, ovšem na pohled druhý je patrné, že jak ve funkci squares
, tak ve funkci removeDivisibleBy3
vytváříme "zbytečné" pole. Kdybych navíc chtěl vypsat například jen první tři hodnoty, pak je mrhání zdroji ještě markantnější.
<?php
$i = 0;
foreach (removeDivisibleBy3(squares(1, 10)) as $value) {
echo $value;
if (++$i > 2) {
break;
}
}
// 1, 4, 16
Můžete namítnout, že kdyby se všechno vykonalo v jednom for kroku, tak by to bylo mnohem efektivnější - máte sice pravdu, ale cílem je mít nejen efektivní program, ale i znovupoužitelné funkce.
První generátor
Pojďme přepsat předchozí příklad tak, abychom použili generátor:
<?php
function squares($from, $to)
{
for (; $from < $to; $from++) {
yield $from * $from;
}
}
foreach (squares(1, 10) as $value) {
echo $value;
}
// 1, 4, 9, 16, 25, 36, 49, 64, 81
Hlavní změnou oproti původní implementaci je použití použití operátoru yield
, který nedělá nic jiného, než že výsledky funkce posílá průběžně, tedy bez potřeby uložit výsledek do paměti. Stejně tak můžeme upravit implementaci funkce removeDivisibleBy3
:
<?php
function removeDivisibleBy3($values)
{
foreach ($values as $value) {
if ($value % 3 != 0) {
yield $value;
}
}
}
Generátory jsou skvělé a měli byste je použít v případě, že se chystáte pracovat s obrovskými sety a zároveň nechcete/nemůžete alokovat potřebné množství paměti pro získané výsledky, pracovat s jakoukoliv posloupností nebo když nevíte, jestli získané výsledky budete potřebovat všechny. Díky tomu, jak generátory fungují, můžete stáhnout paměťovou náročnost vašeho programu jenom na to, co je potřeba k získání jednoho výsledku: