it-swarm.com.ru

Что значит урожайность в PHP?

Я недавно наткнулся на этот код:

function xrange($min, $max) 
{
    for ($i = $min; $i <= $max; $i++) {
        yield $i;
    }
}

Я никогда не видел это ключевое слово yield раньше. Пытаясь запустить код, который я получаю

Ошибка разбора: синтаксическая ошибка, неожиданный T_VARIABLE в строке x

Так что же это за ключевое слово yield? Это даже действительный PHP? И если это так, как я могу его использовать?

192
Gordon

Что такое yield?

Ключевое слово yieldвозвращает данные из функции генератора:

Сердцем функции генератора является ключевое слово yield. В своей простейшей форме оператор yield очень похож на оператор return, за исключением того, что вместо остановки выполнения функции и возврата yield вместо этого предоставляет значение для кода, зацикливающегося над генератором, и приостанавливает выполнение функции генератора.

Что такое функция генератора?

Функция генератора - фактически более компактный и эффективный способ написать Iterator . Он позволяет вам определить функцию (ваше xrange), которая будет вычислять и возвращать значения , в то время как вы циклично она :

foreach (xrange(1, 10) as $key => $value) {
    echo "$key => $value", PHP_EOL;
}

Это создаст следующий вывод:

0 => 1
1 => 2
…
9 => 10

Вы также можете контролировать $key в foreach, используя

yield $someKey => $someValue;

В функции генератора $someKey - это то, что вы хотите отобразить для $key, а $someValue - это значение в $val. В примере вопроса это $i.

В чем разница с обычными функциями?

Теперь вы можете задаться вопросом, почему мы не просто используем PHP --- range function для достижения этого результата. И ты прав. Вывод будет таким же. Разница в том, как мы туда попали.

Когда мы используем range PHP, мы его запустим, создадим весь массив чисел в памяти и return, который весь массив , в цикл foreach, который затем пройдет по нему и выведет значения. Другими словами, foreach будет работать с самим массивом. Функция range и foreach только "говорят" один раз. Думайте об этом как о получении посылки по почте. Курьер доставит вам посылку и уйдет. А затем вы разворачиваете весь пакет, вынимая все, что там есть.

Когда мы используем функцию генератора, PHP будет входить в функцию и выполнять ее, пока она не встретит конец или ключевое слово yield. Когда он встречает yield, он будет возвращать то, что является значением в то время, во внешний цикл. Затем он возвращается в функцию генератора и продолжает с того места, где он уступил. Так как ваш xrange содержит цикл for, он будет выполняться и работать до тех пор, пока не будет достигнут $max. Думайте об этом как foreach и генератор, играющий в пинг-понг.

Зачем мне это?

Очевидно, что генераторы можно использовать для обхода ограничений памяти. В зависимости от вашей среды выполнение range(1, 1000000) приведет к фатальному сценарию, тогда как то же самое с генератором будет работать нормально. Или, как говорит Википедия:

Поскольку генераторы вычисляют свои полученные значения только по требованию, они полезны для представления последовательностей, которые было бы дорого или невозможно вычислить сразу. К ним относятся, например, бесконечные последовательности и живые потоки данных.

Генераторы также должны быть довольно быстрыми. Но имейте в виду, что когда мы говорим о быстром, мы обычно говорим в очень небольших количествах. Поэтому, прежде чем вы сейчас запустите и измените весь свой код на использование генераторов, сделайте тест, чтобы увидеть, где это имеет смысл.

Другой вариант использования для генераторов - асинхронные сопрограммы. Ключевое слово yield не только возвращает значения, но и принимает их. Подробнее об этом см. Два превосходных поста в блоге, ссылки на которые приведены ниже.

С каких это пор я могу использовать yield?

Генераторы были введены в PHP 5.5 . Попытка использовать yield до этой версии приведет к различным ошибкам синтаксического анализа, в зависимости от кода, который следует за ключевым словом. Поэтому, если вы получили ошибку разбора этого кода, обновите ваш PHP.

Источники и дальнейшее чтение:

286
Gordon

Эта функция использует yield:

function a($items) {
    foreach ($items as $item) {
        yield $item + 1;
    }
}

почти такой же, как этот без:

function b($items) {
    $result = [];
    foreach ($items as $item) {
        $result[] = $item + 1;
    }
    return $result;
}

С одной лишь разницей, что a() возвращает генератор и b() просто массив. Вы можете повторить и то и другое.

Кроме того, первый не выделяет полный массив и поэтому требует меньше памяти.

25
tsusanka

Ключевое слово yield служит для определения "генераторов" в PHP 5.5. Хорошо, тогда что такое генератор ?

С php.net:

Генераторы предоставляют простой способ реализации простых итераторов без дополнительных затрат и сложности реализации класса, реализующего интерфейс итератора.

Генератор позволяет вам писать код, который использует foreach для итерации по набору данных без необходимости создавать массив в памяти, что может привести к превышению лимита памяти или значительному времени обработки для генерации. Вместо этого вы можете написать функцию генератора, которая аналогична обычной функции, за исключением того, что вместо однократного возврата генератор может выдавать столько раз, сколько ему нужно, чтобы предоставить значения для повторения.

Отсюда: генераторы = генераторы, другие функции (просто простые функции) = функции.

Итак, они полезны, когда:

  • вам нужно делать простые вещи (или простые вещи);

    генератор действительно намного проще, чем реализация интерфейса Iterator. с другой стороны, конечно, что генераторы менее функциональны. сравните их .

  • вам нужно генерировать большие объемы данных - экономя память;

    фактически для экономии памяти мы можем просто генерировать необходимые данные с помощью функций для каждой итерации цикла, а после итерации использовать мусор. Итак, основные моменты - понятный код и, возможно, производительность. посмотрим, что лучше для ваших нужд.

  • вам нужно сгенерировать последовательность, которая зависит от промежуточных значений;

    это расширение предыдущей мысли. генераторы могут сделать вещи проще по сравнению с функциями. проверьте пример Фибоначчи и попробуйте создать последовательность без генератора. Также генераторы могут работать быстрее в этом случае, по крайней мере, из-за сохранения промежуточных значений в локальных переменных;

  • вам нужно улучшить производительность.

    в некоторых случаях они могут работать быстрее, чем функции (см. предыдущее преимущество);

20
QArea

простой пример

<?php
echo '#start main# ';
function a(){
    echo '{start[';
    for($i=1; $i<=9; $i++)
        yield $i;
    echo ']end} ';
}
foreach(a() as $v)
    echo $v.',';
echo '#end main#';
?>

Результат

#start main# {start[1,2,3,4,5,6,7,8,9,]end} #end main#
19
Think Big

С yield вы можете легко описать точки останова между несколькими задачами в одной функции. Вот и все, в этом нет ничего особенного.

$closure = function ($injected1, $injected2, ...){
    $returned = array();
    //task1 on $injected1
    $returned[] = $returned1;
//I need a breakpoint here!!!!!!!!!!!!!!!!!!!!!!!!!
    //task2 on $injected2
    $returned[] = $returned2;
    //...
    return $returned;
};
$returned = $closure($injected1, $injected2, ...);

Если task1 и task2 тесно связаны, но вам нужна точка останова между ними, чтобы сделать что-то еще:

  • свободная память между строками базы данных
  • запускать другие задачи, которые обеспечивают зависимость от следующей задачи, но которые не связаны с пониманием текущего кода
  • делать асинхронные вызовы и ждать результатов
  • и так далее ...

тогда генераторы - лучшее решение, потому что вам не нужно разбивать ваш код на множество замыканий или смешивать его с другим кодом, или использовать обратные вызовы и т. д. Вы просто используете yield, чтобы добавить точку останова, и вы можете продолжить с эта точка останова, если вы готовы.

Добавить точку останова без генераторов:

$closure1 = function ($injected1){
    //task1 on $injected1
    return $returned1;
};
$closure2 = function ($injected2){
    //task2 on $injected2
    return $returned1;
};
//...
$returned1 = $closure1($injected1);
//breakpoint between task1 and task2
$returned2 = $closure2($injected2);
//...

Добавить точку останова с генераторами

$closure = function (){
    $injected1 = yield;
    //task1 on $injected1
    $injected2 = (yield($returned1));
    //task2 on $injected2
    $injected3 = (yield($returned2));
    //...
    yield($returnedN);
};
$generator = $closure();
$returned1 = $generator->send($injected1);
//breakpoint between task1 and task2
$returned2 = $generator->send($injected2);
//...
$returnedN = $generator->send($injectedN);

примечание: с генераторами легко ошибиться, поэтому всегда пишите модульные тесты перед их реализацией!примечание 2: Использование генераторов в бесконечном цикле похоже на написание замыкания, имеющего бесконечную длину. ..

6
inf3rno

Интересный аспект, который стоит обсудить здесь, --- уступает по ссылке. Каждый раз, когда нам нужно изменить параметр таким образом, чтобы он отражался вне функции, мы должны передавать этот параметр по ссылке. Чтобы применить это к генераторам, мы просто добавляем амперсанд & к имени генератора и к переменной, используемой в итерации:

 <?php 
 /**
 * Yields by reference.
 * @param int $from
 */
function &counter($from) {
    while ($from > 0) {
        yield $from;
    }
}

foreach (counter(100) as &$value) {
    $value--;
    echo $value . '...';
}

// Output: 99...98...97...96...95...

В приведенном выше примере показано, как изменение повторяющихся значений в цикле foreach изменяет переменную $from в генераторе. Это потому, что $from --- получено по ссылке из-за амперсанда перед именем генератора. По этой причине переменная $value в цикле foreach является ссылкой на переменную $from в функции генератора.

0
bodi0