yield and Generator of php and its application

yield and Generator of php and its application

Reference resources

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 locationabstract public scalar key ( void )      //Return the key corresponding to the current elementabstract public void next ( void )       //Move to next elementabstract public void rewind ( void )     //Back to the first elementabstract 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.

Published 9 original articles, won praise 0, visited 2263
Private letter follow

Keywords: PHP Laravel

Added by nomanoma on Sun, 19 Jan 2020 10:04:29 +0200