Я недавно наткнулся на этот код:
function xrange($min, $max) { for ($i = $min; $i <= $max; $i++) { yield $i; } }
Я никогда не видел это ключевое слово yield
. Пытаясь запустить код, который я получаю
Ошибка анализа: синтаксическая ошибка, неожиданный T_VARIABLE в строке x
Итак, каково это ключевое слово yield
? Действительно ли это PHP? И если это так, как мне его использовать?
yield
? Ключевое слово yield
возвращает данные из функции генератора:
Сердцем функции генератора является ключевое слово yield. В своей простейшей форме оператор yield похож на оператор return, за исключением того, что вместо того, чтобы прекратить выполнение функции и возвращать, yield вместо этого дает значение для цикла кода над генератором и приостанавливает выполнение функции генератора.
Функция генератора представляет собой более компактный и эффективный способ записи итератора . Он позволяет вам определить функцию (ваш 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
.
Теперь вы можете задаться вопросом, почему мы не просто используем собственную функцию range
PHP для достижения этого результата. И ты прав. Результат будет таким же. Разница в том, как мы туда попали.
Когда мы будем использовать range
PHP, он выполнит его, создаст весь массив чисел в памяти и return
весь массив в цикл foreach
который затем переместит его и выведет значения. Другими словами, foreach
будет работать на самом массиве. Функция range
и foreach
только «разговора» один раз. Подумайте, как получить пакет по почте. Парень доставки подаст вам пакет и уйдет. И затем вы разворачиваете весь пакет, вынимая все, что там.
Когда мы используем функцию генератора, PHP будет входить в функцию и выполнять ее до тех пор, пока она не встретит конец или ключевое слово yield
. Когда он удовлетворяет yield
, он затем вернет то, что является значением в это время для внешнего цикла. Затем он возвращается в функцию генератора и продолжается от того места, где он уступил. Поскольку ваш xrange
имеет цикл for
, он будет выполняться и уступать до достижения $max
. Подумайте об этом, как в foreach
и генераторе, играющем в пинг-понг.
Очевидно, что генераторы могут использоваться для ограничения объема памяти. В зависимости от вашей среды выполнение range(1, 1000000)
приведет к фатальному сценарию, тогда как то же самое с генератором будет работать нормально. Или, как пишет Википедия:
Поскольку генераторы вычисляют полученные значения только по требованию, они полезны для представления последовательностей, которые были бы дорогими или невозможными для вычисления сразу. К ним относятся, например, бесконечные последовательности и потоки живых данных.
Генераторы также должны быть довольно быстрыми. Но имейте в виду, что, когда мы говорим о быстром, мы обычно говорим в очень маленьких количествах. Поэтому перед тем, как вы убежите и измените весь свой код на использование генераторов, выполните контрольный тест, чтобы узнать, где это имеет смысл.
Другой пример использования для генераторов – это асинхронные сопрограммы. Ключевое слово yield
не только возвращает значения, но также принимает их. Подробнее об этом см. В двух замечательных сообщениях в блоге, приведенных ниже.
yield
? Генераторы были представлены в PHP 5.5 . Попытка использовать yield
до этой версии приведет к различным ошибкам разбора, в зависимости от кода, следующего за ключевым словом. Поэтому, если вы получите ошибку синтаксического анализа из этого кода, обновите свой PHP.
ключевое слово yield
служит для определения «генераторов» в PHP 5.5. Хорошо, тогда что такое генератор ?
От php.net:
Генераторы обеспечивают простой способ реализации простых итераторов без накладных расходов или сложности реализации класса, реализующего интерфейс Iterator.
Генератор позволяет вам писать код, который использует foreach для итерации по набору данных без необходимости создания массива в памяти, что может привести к превышению лимита памяти или потребует значительного количества времени обработки для генерации. Вместо этого вы можете написать генераторную функцию, которая является такой же, как и для нормальной функции, за исключением того, что вместо того, чтобы возвращаться один раз, генератор может давать столько раз, сколько необходимо, чтобы обеспечить повторение итераций.
Из этого места: генераторы = генераторы, другие функции (простые функции) = функции.
Таким образом, они полезны, когда:
вам нужно сделать что-то простое (или простые вещи);
генератор действительно намного проще, чем реализация интерфейса Iterator. другая рука, источник, что генераторы менее функциональны. сравните их .
вам необходимо создать БОЛЬШИЕ объемы памяти, сохраняющей данные;
на самом деле для экономии памяти мы можем просто генерировать необходимые данные через функции для каждой итерации цикла, а после итерации использовать мусор. поэтому здесь основные моменты – четкий код и, вероятно, производительность. посмотрите, что лучше для ваших нужд.
вам необходимо сгенерировать последовательность, которая зависит от промежуточных значений;
это продолжение предыдущей мысли. генераторы могут упростить работу по сравнению с функциями. проверьте пример Фибоначчи и попробуйте сделать последовательность без генератора. В этом случае генераторы могут работать быстрее, по крайней мере, из-за хранения промежуточных значений в локальных переменных;
вам нужно повысить производительность.
они могут работать быстрее, чем функции в некоторых случаях (см. предыдущее преимущество);
простой пример
<?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#
С 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: Использование генераторов в бесконечном цикле подобно записи замыкания с бесконечной длиной …
Эта функция использует выход:
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()
только простой массив. Вы можете, однако, повторить и то и другое, что является важным аспектом.