Что означает доходность в PHP?

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

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

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

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

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

Ответ 1

Что такое 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 native range function для достижения этого результата. И ты прав. Результат будет таким же. Разница в том, как мы туда попали.

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

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

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

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

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

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

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

С каких пор можно использовать yield?

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

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

Ответ 2

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

От php.net:

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

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

Из этого места: генераторы = генераторы, другие функции (просто простые функции) = функции.

Таким образом, они полезны, когда:

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

    Генератор

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

  • вам нужно создать БОЛЬШИЕ объемы памяти, сохраняющей данные;

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

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

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

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

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

Ответ 3

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

<?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#

Ответ 4

С помощью 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);

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

Ответ 5

Эта функция использует выход:

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

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

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

С единственным отличием, что a() возвращает generator и b() просто простой массив. Вы можете, однако, итератировать на обоих, что является важным аспектом.