這篇文章假設你有下列的知識:
如果說一般用「return」的函式是只能發射一次,但能重複利用的橡皮筋;
那麼用「yield」的函式就是連弩了吧XD
在這篇文章我會試著從 yield 的原理解釋為什麼它可以達成多次 return 的效果
<?php
// 這個函式會回傳一個 Generator 的物件
function g()
{
foreach(range(1, 10) as $val) {
// yield 就像函式的中斷點,每次執行到這就會回傳 $val 並中斷函式的執行,
// 直到下次執行這個函式。
yield $val;
}
}
// 這個物件是可以被迭代的,每一次的迭代都會執行到 yield 那行為止。
$generator = g();
// 你應該會好奇:在 g() 裡面只有回傳 $val 而沒有 $key。那 $key 是從何而來的?
// 這是因為 Generator 會為 $val 產生一個對應整數,並當成是它的 $key。
foreach ($generator as $key => $val) {
echo "{$key}: {$val}" . PHP_EOL;
}
// output:
// 0: 1
// 1: 2
// 2: 3
// 3: 4
// 4: 5
// 5: 6
// 6: 7
// 7: 8
// 8: 9
// 9: 10
<?php
function g()
{
foreach(range(1, 10) as $val) {
yield ($val + 1000) => $val;
}
}
// generator 因為有實作 Iterator 界面,所以可以被用來迭代
foreach (g() as $key => $val) {
echo "{$key}: {$val}" . PHP_EOL;
}
// output:
// 1001: 1
// 1002: 2
// 1003: 3
// 1004: 4
// 1005: 5
// 1006: 6
// 1007: 7
// 1008: 8
// 1009: 9
// 1010: 10
這語法就像我們在 foreach 那邊使用的一樣,很簡單吧!
generator 不能用 return 從函式回傳任何值,
只可以用「return;」中斷執行
用 yield 最大的好處就在它相當節省記憶體了!
(而且語法還基本上跟一般用 return 的函式保持一致)
比如如同在 PHP 官網中舉的例子:
如果你使用 range(0, 1000000)
時會吃掉超過 100 MB的記憶體,
但他們用 yield 重製了一個 xrange()
,
結果居然只吃不到 1 KB 的記憶體!
範例程式碼請到這裡看
比如說你在 generator(yield function) 中讀取一個檔案,
然後要把它一行一行的印出來...,總之你最後需要執行 fclose()
對吧?
但如果一個 generator 在 yield 之前,
就發生了異常或被 return; 強制結束。
那麼要如何關閉那個檔案呢?
根據 nikita popov 在這裡的說法:
...
在上述的情況下,如果一個 generator 當中有包含 finally 這個區塊,
那麽 finally 區塊會在這些情況發生時被執行。
...
所以你就這樣寫就對了:
<?php
// @link http://php.net/manual/en/language.generators.overview.php#112985
function getLines($file) {
$f = fopen($file, 'r');
try {
while ($line = fgets($f)) {
yield $line;
}
} finally {
fclose($f);
}
}