繼續
上次給大家講了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:
列印出 $re
是 NULL
, list($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 協議》,轉載必須註明作者和本文連結