What does yield mean in PHP?

北城余情 提交于 2019-12-17 04:10:52

问题


I've recently stumbled over this code:

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

I've never seen this yield keyword before. Trying to run the code I get

Parse error: syntax error, unexpected T_VARIABLE on line x

So what is this yield keyword? Is it even valid PHP? And if it is, how do I use it?


回答1:


What is yield?

The yield keyword returns data from a generator function:

The heart of a generator function is the yield keyword. In its simplest form, a yield statement looks much like a return statement, except that instead of stopping execution of the function and returning, yield instead provides a value to the code looping over the generator and pauses execution of the generator function.

What is a generator function?

A generator function is effectively a more compact and efficient way to write an Iterator. It allows you to define a function (your xrange) that will calculate and return values while you are looping over it:

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

This would create the following output:

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

You can also control the $key in the foreach by using

yield $someKey => $someValue;

In the generator function, $someKey is whatever you want appear for $key and $someValue being the value in $val. In the question's example that's $i.

What's the difference to normal functions?

Now you might wonder why we are not simply using PHP's native range function to achieve that output. And right you are. The output would be the same. The difference is how we got there.

When we use range PHP, will execute it, create the entire array of numbers in memory and return that entire array to the foreach loop which will then go over it and output the values. In other words, the foreach will operate on the array itself. The range function and the foreach only "talk" once. Think of it like getting a package in the mail. The delivery guy will hand you the package and leave. And then you unwrap the entire package, taking out whatever is in there.

When we use the generator function, PHP will step into the function and execute it until it either meets the end or a yield keyword. When it meets a yield, it will then return whatever is the value at that time to the outer loop. Then it goes back into the generator function and continues from where it yielded. Since your xrange holds a for loop, it will execute and yield until $max was reached. Think of it like the foreach and the generator playing ping pong.

Why do I need that?

Obviously, generators can be used to work around memory limits. Depending on your environment, doing a range(1, 1000000) will fatal your script whereas the same with a generator will just work fine. Or as Wikipedia puts it:

Because generators compute their yielded values only on demand, they are useful for representing sequences that would be expensive or impossible to compute at once. These include e.g. infinite sequences and live data streams.

Generators are also supposed to be pretty fast. But keep in mind that when we are talking about fast, we are usually talking in very small numbers. So before you now run off and change all your code to use generators, do a benchmark to see where it makes sense.

Another Use Case for Generators is asynchronous coroutines. The yield keyword does not only return values but it also accepts them. For details on this, see the two excellent blog posts linked below.

Since when can I use yield?

Generators have been introduced in PHP 5.5. Trying to use yield before that version will result in various parse errors, depending on the code that follows the keyword. So if you get a parse error from that code, update your PHP.

Sources and further reading:

  • Official docs
  • The original RFC
  • kelunik's blog: An introduction to generators
  • ircmaxell's blog: What generators can do for you
  • NikiC's blog: Cooperative multitasking using coroutines in PHP
  • Co-operative PHP Multitasking
  • What is the difference between a generator and an array?
  • Wikipedia on Generators in general



回答2:


This function using yield:

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

is almost the same as this one without:

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

With only one difference that a() returns a generator and b() just a simple array. You can iterate on both.

Also, the first one does not allocate a full array and is therefore less memory-demanding.




回答3:


yield keyword serves for definition of "generators" in PHP 5.5. Ok, then what is a generator?

From php.net:

Generators provide an easy way to implement simple iterators without the overhead or complexity of implementing a class that implements the Iterator interface.

A generator allows you to write code that uses foreach to iterate over a set of data without needing to build an array in memory, which may cause you to exceed a memory limit, or require a considerable amount of processing time to generate. Instead, you can write a generator function, which is the same as a normal function, except that instead of returning once, a generator can yield as many times as it needs to in order to provide the values to be iterated over.

From this place: generators = generators, other functions (just a simple functions) = functions.

So, they are useful when:

  • you need to do things simple (or simple things);

    generator is really much simplier then implementing the Iterator interface. other hand is, ofcource, that generators are less functional. compare them.

  • you need to generate BIG amounts of data - saving memory;

    actually to save memory we can just generate needed data via functions for every loop iteration, and after iteration utilize garbage. so here main points is - clear code and probably performance. see what is better for your needs.

  • you need to generate sequence, which depends on intermediate values;

    this is extending of the previous thought. generators can make things easier in comparison with functions. check Fibonacci example, and try to make sequence without generator. Also generators can work faster is this case, at least because of storing intermediate values in local variables;

  • you need to improve performance.

    they can work faster then functions in some cases (see previous benefit);




回答4:


simple example

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

output

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



回答5:


With yield you can easily describe the breakpoints between multiple tasks in a single function. That's all, there is nothing special about it.

$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, ...);

If task1 and task2 are highly related, but you need a breakpoint between them to do something else:

  • free memory between processing database rows
  • run other tasks which provide dependency to the next task, but which are unrelated by understanding the current code
  • doing async calls and wait for the results
  • and so on ...

then generators are the best solution, because you don't have to split up your code into many closures or mix it with other code, or use callbacks, etc... You just use yield to add a breakpoint, and you can continue from that breakpoint if you are ready.

Add breakpoint without generators:

$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);
//...

Add breakpoint with generators

$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);

note: It is easy to make mistake with generators, so always write unit tests before you implement them! note2: Using generators in an infinite loop is like writing a closure which has infinite length...




回答6:


An interesting aspect, which worth to be discussed here, is yielding by reference. Every time we need to change a parameter such that it is reflected outside of the function, we have to pass this parameter by reference. To apply this to generators, we simply prepend an ampersand & to the name of the generator and to the variable used in the iteration:

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

The above example shows how changing the iterated values within the foreach loop changes the $from variable within the generator. This is because $from is yielded by reference due to the ampersand before the generator name. Because of that, the $value variable within the foreach loop is a reference to the $from variable within the generator function.



来源:https://stackoverflow.com/questions/17483806/what-does-yield-mean-in-php

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!