yield and Generator of php and its application
When viewing the laravel source code, you can see the following code
vendor\laravel\framework\src\Illuminate\Container\Container.php
public function tagged($tag) { if (! isset($this->tags[$tag])) { return []; } return new RewindableGenerator(function () use ($tag) { foreach ($this->tags[$tag] as $abstract) { yield $this->make($abstract); } }, count($this->tags[$tag])); }
What is yield?
Official explanation As follows:
Generator syntax
When a generator is called, it returns an object that can be traversed. When you traverse the object (for example, through a foreach), you will call the generator function every time you need a value, and save the state of the generator after generating a value, so that it can recover the call state when it needs to generate the next value.
Once you no longer need to generate more values, the generator function can simply exit, and the code calling the generator can continue to execute, just like an array has been traversed.
The core of the generator function is the yield keyword. Its simplest form of call looks like a return declaration. The difference is that ordinary return will return a value and terminate the execution of the function, while yield will return a value to the code that circulates to call the generator and only pauses the execution of the generator function.
It looks obscure. It's a bit like return?
So let's write the simplest test first. The code is as follows:
<?php function gen() { yield 1; } var_export(gen()); /* * return * Generator::__set_state(array( ))*/
So what is a Generator generator?
Generator provides a convenient way to implement a simple Iterator (Iterator). Using generator to implement Iterator does not need to create a class to inherit the Iterator interface.
So what is Iterator iterator
Object traversal needs to be implemented Iterator 5 methods provided by the interface:
Iterator extends Traversable { /* Methods */ abstract public mixed current ( void ) //Returns the element at the current location abstract public scalar key ( void ) //Return the key corresponding to the current element abstract public void next ( void ) //Move to next element abstract public void rewind ( void ) //Back to the first element abstract public boolean valid ( void ) //Judge whether the current position is valid }
From the above code, we can see that Iterator inherits Traversable. What is Traversable
Traversable is an empty interface, a flag, indicating whether a non array variable can be traversed by foreach
Generally, the following code can be used to determine whether a variable can be traversed through foreach
<?php if( !is_array( $items ) && !$items instanceof Traversable s) //Throw exception here ?>
Back to the beginning, what is Generator
Let's start with an example
function xrange($start, $end, $step = 1) { for ($i = $start; $i <= $end; $i += $step) { yield $i; } } foreach (xrange(1, 10) as $num) { echo $num, "\n"; } //return /* 1 2 3 4 5 6 7 8 9 10 */
The final effect of the above code is equivalent to
foreach (range(1, 10) as $num) { echo $num, "\n"; } //return /* 1 2 3 4 5 6 7 8 9 10 */
What's the difference?? The difference between the two is that range will return an array containing all elements at one time, while xrange is returned one at a time by iteration during the traversal process. The reason why it can do this is that calling xrange returns a Generator object. Let's add a few lines of code in the above code:
function xrange($start, $end, $step = 1) { for ($i = $start; $i <= $end; $i += $step) { yield $i; } } $range = xrange(1, 10); foreach ($range as $num) { echo $num, "\n"; } var_dump($range); // object(Generator)#1 var_dump($range instanceof Iterator); // bool(true)
You can see that xrange returns a Generator object that inherits the Iterator
Looking at the documentation, the Generator object is defined as follows
Generator implements Iterator { /* Methods */ public mixed current ( void ) public mixed key ( void ) public void next ( void ) public void rewind ( void ) public mixed send ( mixed $value ) public mixed throw ( Exception $exception ) public bool valid ( void ) public void __wakeup ( void ) }
Three more ways than Iterator
_wakeup is a magic method for serialization. Generator implements this method to prevent serialization
throw doesn't work
send is used to pass in parameters to the generator, which will be described later
Generator has another feature. It is a class, but it cannot be instantiated through the new method
The test code is as follows
$g = new Generator(); /* PHP Fatal error: Uncaught Error: The "Generator" class is reserved for internal use and cannot be manually instantiated in **** This means that the Generator class is only for internal use and cannot be initialized manually */
Go back to yield and know the internal implementation method of Generator. You can test the following code
<?php function gen() { yield 1; } $g = gen(); var_export($g->valid()); //true echo "\r\n"; var_export($g->current());; //1 echo "\r\n"; $g->next(); var_export($g->valid());; //false echo "\r\n"; var_export($g->current());; //NULL echo "\r\n"; /* true 1 false NULL */
Variable $g is a generator object, inheriting Iterator. We can call Iterator's method manually without foreach. After the next() method is called, the object has been iterated, so the subsequent return is empty, which meets the expectation;
Similarly, the following code is not difficult to understand:
<?php function gen() { yield 1; yield 2; yield 3; } $g = gen(); var_export($g->valid()); var_export( $g->current()); echo "\n"; echo $g->next(); var_export( $g->valid()); var_export( $g->current()); echo "\n"; echo $g->next(); var_export( $g->valid()); var_export( $g->current()); echo "\n"; echo $g->next(); var_export( $g->valid()); var_export( $g->current()); echo "\n"; /* true1 true2 true3 falseNULL */
Looking at the top part of the code, you can see the difference between yield and return. Return only returns once, and the value is fixed. Yield can return subsequent values as it iterates.
What's the difference between return and return? yield can only be used in functions. The code is as follows
<?php return "aaa";
<?php yield "aaa"; /* PHP Fatal error: The "yield" expression can only be used inside a function in /vagrant/testx/t_yield.php on line 2 */
A small problem: foreach returns $key = > the above examples only return value. The above examples only return value. Can we also return $key
Of course, examples are as follows:
<?php function gen() { yield 1 => "a"; yield 2 => "b"; yield 3 => "c"; } $g = gen(); var_export($g->key()); var_export( $g->current()); echo "\n"; echo $g->next(); var_export( $g->key()); var_export( $g->current()); echo "\n"; echo $g->next(); var_export( $g->key()); var_export( $g->current()); echo "\n"; echo $g->next(); var_export( $g->key()); var_export( $g->current()); echo "\n"; /* 1'a' 2'b' 3'c' NULLNULL */
Another small problem: how do they execute when adding other logic to the generator
<?php function gen() { yield 'yield1'; echo "round1\r\n"; yield 'yield2'; echo "round2\r\n"; } $g = gen(); var_export( $g->current()); //yield1 echo "\n"; $g->next(); //round1 var_export( $g->current()); //yield2 echo "\n"; $g->next(); //round2 var_export( $g->current()); //NULL echo "\n"; /* 'yield1' round1 'yield2' round2 NULL */
As you can see, when calling the next() method, the program will look down for the next yield keyword, and the code before the next yield location will be executed
As mentioned above, send(mixed value), what's the method? What's the method? What's the value? What's the value
First, yield can also be used in the context of an expression, such as the right side of an assignment statement, as follows (according to the above experiment, this code must be written in a function):
$data = (yield $value);
It can be understood that an expression is assigned to $data, so it needs to be enclosed in brackets
Compare the following codes
function gen() { $ret = (yield 'yield1'); var_dump($ret); $ret = (yield 'yield2'); var_dump($ret); } $g = gen(); var_dump($g->current()); $g->next(); var_dump($g->current()); /* /vagrant/testx/t_yield.php:9: string(6) "yield1" /vagrant/testx/t_yield.php:4: NULL /vagrant/testx/t_yield.php:11: string(6) "yield2" */
function gen() { $ret = (yield 'yield1'); var_dump($ret); $ret = (yield 'yield2'); var_dump($ret); } $g = gen(); var_dump($g->current()); var_dump($g->send('ret1')); /* /vagrant/testx/t_yield.php:21: string(6) "yield1" /vagrant/testx/t_yield.php:16: string(4) "ret1" /vagrant/testx/t_yield.php:22: string(6) "yield2" */
Compared with the output, it is easy to find that send($value) implements the function of passing in parameters to the Generator object as the result of the current yield expression, and then executes the next+current method,
What can a Generator do
1. Obviously, to return a large array, it is better to use the generator to save memory, or the above code, because the xrang() method generates the number to be returned in real time when it is called, while the range() method directly generates the array
function xrange($start, $end, $step = 1) { for ($i = $start; $i <= $end; $i += $step) { yield $i; } } foreach (xrange(1, 10) as $num) { echo $num, "\n"; } foreach (range(1, 10) as $num) { echo $num, "\n"; }
2. Multithreading,
Each iteration of traversing the generator object will only execute the code after the previous yield statement, and when the yield statement is encountered, it will return a value, which is equivalent to returning from the generator function. This is a bit like suspending the execution of a process (thread) (yield is used to suspend a process (thread) in many languages), and then starting it to continue to execute, which goes on and on The execution of a program (thread) is aborted, which is why the generator can be used to implement a coroutine.