7

WX20200617-144832.png

继续

上次给大家讲了PHP yield 的用法,今天给大家讲讲 yield from

看到这里来,一定是 PHP.net 看得不过瘾吧,这篇文章一定把 yield from 语法给你讲透彻。

三部曲

语法

<?php
function func()
{
    yield from $expression;
}

是的,和yield一样,这是一个生成器语法。$expression 是必须(yield 可以为空为NULL),且必须是可迭代对象。

快速上手

小例子1

<?php
function yield_from_func()
{
    (yield from array(1, 2, 3, 4));
}

foreach (yield_from_func() as $value)
{
    echo 'value is : ' . $value . PHP_EOL;
}
  • 运行输出
$ php ./test.php

value is : 1
value is : 2
value is : 3
value is : 4

通过以上例子,可得 yield from 能把个数组(也可以是迭代器)一个个遍历并送出来。

小例子2

<?php
function yield_func()
{
    yield 1;
    yield 2;
    yield 3;
}

function yield_from_func2()
{
    (yield from yield_func());
}

$gen = yield_from_func2();
echo 'value is : ' . $gen->current() . PHP_EOL;
$gen->next();
echo 'value is : ' . $gen->current() . PHP_EOL;
$gen->next();
echo 'value is : ' . $gen->current() . PHP_EOL;
$gen->next();
  • 运行输出
$ php ./test.php

value is : 1
value is : 2
value is : 3

小例子2里,yield from 右侧是生成器时, 调用nextcurrent 也能将生成器内的元素一个个送出。

小例子3

<?php

function yield_func()
{
    echo 'run yield_func' . PHP_EOL;
    $get = (yield 12);
    echo $get . PHP_EOL;
    $get2 = (yield 55);
    echo $get2 . PHP_EOL;
    return 'a';
}

function yield_from_func()
{
    echo 'run yield_from_func' . PHP_EOL;
    $re = (yield from yield_func());
    return $re;
}
$gen2 = yield_from_func();

$re = $gen2->current();
echo 'get re: ' . $re . PHP_EOL;
$gen2->send(100);
$re2 = $gen2->current();
echo 'get re2: ' . $re2 . PHP_EOL;
$gen2->send('world');
$re3 = $gen2->getReturn();
echo 'get return: ' . $re3 . PHP_EOL;

运行流程图如下:

gaitubao_新建项目 (1).png

看过我之间那篇文章的读者,这图就非常熟悉了,左边是以前的,最右侧是新加的 yield_from_func() ,对比发现,在外部调用 yield_from_func()current(), send() 和直接调用 yield_func()current(), send()的结果是一样的。yield from 他就是原封不动地把生成器的中间值传来传去, 他就是一个桥梁,通过它可以把 current,send,next,传递进去,还能把里面yield的值送出来。

探个究竟

接下来我们,上全量测试代码。 gitee PHP Generator Yield Demo ,探究一下yield from的各种细节。

git clone https://gitee.com/xupaul/PHP-generator-yield-Demo.git

纸上得来终觉浅,绝知此事要躬行

运行

$ php ./yieldFromTest.php

什么是生成器?

// ./yieldFromFunctions.php
function yield_from_func1()
{
    // yield from ;            // Parse error: syntax error, unexpected ';'
}

有一行代码,被注释了,语法静态检查都不能通过。

20200526150915.png

上图是生成器判断运行结果,yield_from_func4(), 有个if判断是否运行到 yield from, PHP 依然判定为生成器。

其实到这里,大家一下就清楚了,是不是生成器是静态判断的。

<?php
function yield_from_func3()
{
    yield from 'test';
}
···
echo 'eg: NO.5' . PHP_EOL;
$gen = yield_from_func3();
echo 'call yield_from_func2 current ' . PHP_EOL;
// $re = $gen->current();          // Fatal error: Uncaught Error: Can use "yield from" only with arrays and Traversables
echo PHP_EOL;
  • 运行结果:
eg: NO.5
yield_from_func4 is PHP Generator? :true
call yield_from_func2 current

yield_from_func4虽然判定为生成器,一执行就报错,可得 yield from 右侧必须是一个可遍历对象。

例子8也会运行出错。

current、next 和 send

20200526151316.png

以上截图,例子1,和例子2,可以看到 yield from 和 yield 的 current,获取当前值,next 跳过, send 向生成器传入值,这些功能和一半生成起都差不多的。

同样,开始时跳过current,直接调用send,会丢失第一次yield的弹出值。

yield from 接收数据

20200526155234.png

例子:6,7.中,看到截图中例子6 get re: 打印出 $reNULLlist($re1,$re2,$re3)同样也是NULL.

结论: yield from 左侧不能收到任何值,在左侧写赋值语句不会报错,但也没有意义。

那么send进入的值,去哪了呢,继续看

yield from 嵌套

<?php
function yield_func20 ()
{
    $arr = array();
    echo 'run to function ' . __FUNCTION__ . ' line: ' . __LINE__ . PHP_EOL;
    $arr[] = yield 2;
    $arr[] = yield 'key' => 'value';
    $arr[] = yield 7 => 'cc';
    $arr[] = yield 5;
    echo 'run to function ' . __FUNCTION__ . ' line: ' . __LINE__ . ', arr re: ';
    var_export($arr);
    echo PHP_EOL;
}

20200526160130.png

例子:9,10中,我们实现了,从生成器中取值。 同时通过 yield_func20 函数内 $arr 变量dump发现,向生成器 send 的值,会通过 yield from 传到内部生成器的 yield 赋值语句.

20200526161612.png

例子:12,13中,发现,yield from 是可以多层嵌套的。

yield from 的嵌套作用,是非常重要特性,在协程编码中,熟练使用嵌套能减少函数指针,以及生成器的应用传递。

总结

yield from 是一个强大且不可缺少的语法,如果只有 yield 那么就只是有了生成器,有了 yield from 那就有了一根强大的“针”——穿过一个个 生成器,按照call stack 把一个个生成器串了起来。 调用方法用 call_user_func(),调用 生成器用 yield from .

好,这就是 yield from 用法的探究,实战会放到后续文章。

欢迎提问,如果有帮助请关注,收藏,作者有新的发现,干货,也会更新文章。

没人比我更懂


小白要生发
1k 声望1.2k 粉丝

GoPHPer工程师