PHP yield from 生成器用法探究 (二)

小白要生髮發表於2020-06-25

繼續

上次給大家講了PHP yield 的用法,今天給大家講講 yield from

看到這裡來,一定是 PHP.net 看得不過癮吧,這篇文章一定把 yield from 語法給你講透徹。

語法

<?php
function func()
{
    $re = (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
<?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` 右側是生成器時, 呼叫`next``current` 也能將生成器內的元素一個個送出。

#### 小例子3

```php
<?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;

執行流程圖如下:

看過我之間那篇文章的讀者,這圖就非常熟悉了,左邊是以前的,最右側是新加的 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 ';'
}

有一行程式碼,被註釋了,語法靜態檢查都不能透過。

上圖是生成器判斷執行結果,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

以上截圖,例子1,和例子2,可以看到 yield from 和 yield 的 current,獲取當前值,next 跳過, send 向生成器傳入值,這些功能和一半生成起都差不多的。

同樣,開始時跳過current,直接呼叫send,會丟失第一次yield的彈出值。

yield from 接收資料

例子:6,7.中,看到截圖中例子6 get re: 列印出 $reNULLlist($re1,$re2,$re3)同樣也是NULL. 則說明什麼,說明了:如果沒有顯示指定返回則是NULL!

在看到例子:14,15,輸出的run to function yield_from_func13 line: 95, var_export re: 211 這一行,列印的是生成器,最後一行return的值。

結論: yield from 左側不能收到 yeild 彈出的值,但實際它接收的是 生成器內 return的值!

那麼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;
}

例子:9,10中,我們實現了,從生成器中取值。 同時透過 yield_func20 函式內 $arr 變數dump發現,向生成器 send 的值,會透過 yield from 傳到內部生成器的 yield 賦值語句.

例子:12,13中,發現,yield from 是可以多層巢狀的。

yield from 的巢狀作用,是非常重要特性,在協程編碼中,熟練使用巢狀能減少函式指標,以及生成器的應用傳遞。

總結

yield from 是一個強大且不可缺少的語法,如果只有 yield 那麼就只是有了生成器,有了 yield from 那就有了一根強大的“針”——穿過一個個 生成器,按照call stack 把一個個生成器串了起來。 呼叫方法用 call_user_func(),呼叫 生成器用 yield from .

yield from 左側的變數接收的值,是右側表示式 return 的值, 右側表示式內 yield 彈出的值,會被 yield from 繼續往外拋。

好,這就是 yield from 用法的探究,實戰會放到後續文章。

歡迎提問,如果有幫助請關注,收藏,作者有新的發現,乾貨,也會更新文章。

沒人比我更懂

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章