Language Reference >> Predefined Interfaces and Classes >> Generator >> send

由於send方法是實現協程的關鍵所在,覺得有必要單獨詳細學習下send方法,因爲確實詭異;

官方文檔

Generator::send

(PHP 5 >= 5.5.0, PHP 7) Generator::send — Send a value to the generator

Description

public mixed Generator::send ( mixed $value )

Sends the given value to the generator as the result of the current yield expression and resumes execution of the generator.
這裏官方定義:send方法,發送做一個值作爲生成器當前執行到的yield表達式的結果值(send方法並不執行當前yield表達式而僅僅是將發送的值作爲當前yield表達式的結果值);並且繼續執行生成器(繼續執行下一次yield);
這裏需要特別注意的是,send發送的值作爲當前執行到的yield表達式的結果值;send返回的值,是下一個yield表達式的結果值;

If the generator is not at a yield expression when this method is called, it will first be let to advance to the first yield expression before sending the value. As such it is not necessary to “prime” PHP generators with a Generator::next() call (like it is done in Python).
如果當前生成器不在yield表達式,它會先自動運行到第一個yield表達式,然後再執行發送$value的操作;因此,沒有必要使用 Generator::next() 來調用PHP生成器(就像在Python中完成的那樣)。
這裏需要特別注意的是,在第一次調用send方法,它會讓生成器自動運行到第一個yield表達式的位置並且執行第一個yield,而後再執行send方法的調用;這裏在下面有例子說明;

Parameters

value

Value to send into the generator. This value will be the return value of the yield expression the generator is currently at.

Return Values

Returns the yielded value.
這個表示返回的值爲執行過的yield表達式的值,但是在運行一次send方法之後,會有可能跨度兩個yield表達式,所以這裏的定義就模糊不明;

EXP1

function task(){
    echo "This is segment 1\n"; // 這裏是第一程序段,在第一個yield之前;
    $a = yield 'step 1';
    echo "Get step 1 send value : $a\n";
    echo "This is segment 2\n"; // 這裏是第二程序段,在第一個yield之後,在第二個yield之前;
    $b = yield 'step 2';
    echo "Get step 2 send value : $b\n";
    echo "This is segment 3\n"; // 這裏是第三程序段,在第二個yield之後;
}
$task = task();
$sendResult = $task->send('first send');
echo "The first send result : $sendResult\n";
/**
 * 輸出:
 * This is segment 1
 * Get step 1 send value : first send
 * This is segment 2
 * The first send result : step 2
 */

根據上例得知:
send方法發送的值 first send 被作爲第一個 yield (step 1) 的結果值;
send方法的返回值是第二個 yield (step 2) 表達式的值;

有疑問:
看到鳥哥的文章裏面說明,當一個生成器方法被調用的時候,rewind方法會自動執行;而PHP官網裏面對remind方法的描述就是簡簡單單的 Generator::rewind — Rewind the iterator,那麼在rewind方法調用的時候,生成器會不會自動執行到第一個yield表達式的位置呢?我們看下個例子,單單將生成器函數賦值給變量;

EXP2

function task(){
    echo "This is segment 1\n"; // 這裏是第一程序段,在第一個yield之前;
    $a = yield 'step 1';
    echo "Get step 1 send value : $a\n";
    echo "This is segment 2\n"; // 這裏是第二程序段,在第一個yield之後,在第二個yield之前;
    $b = yield 'step 2';
    echo "Get step 2 send value : $b\n";
    echo "This is segment 3\n"; // 這裏是第三程序段,在第二個yield之後;
}
$task = task();
/**
 * 輸出:
 */

輸出爲空,可以得知,將生成器函數賦值的時候,生成器裏面的代碼並沒有執行;那就是說,EXP1例子中,代碼的執行,均由send方法造成的;

EXP3

官網對send方法的定義中:

send方法,發送做一個值作爲生成器當前執行到的yield表達式的結果值;並且繼續執行生成器(繼續執行生成器,應該更準確的說明:繼續執行下一次yield);

function task(){
    echo "This is segment 1\n"; // 這裏是第一程序段,在第一個yield之前;
    $a = yield 'step 1';
    echo "Get step 1 send value : $a\n";
    echo "This is segment 2\n"; // 這裏是第二程序段,在第一個yield之後,在第二個yield之前;
    $b = yield 'step 2';
    echo "Get step 2 send value : $b\n";
    echo "This is segment 3\n"; // 這裏是第三程序段,在第二個yield之後;
}
$task = task();
$sendResult = $task->send('first send');
echo "The first send result : $sendResult\n";
$sendResult = $task->send('second send');
echo "The second send result : $sendResult\n";
/**
 * 輸出:
 * This is segment 1
 * Get step 1 send value : first send
 * This is segment 2
 * The first send result : step 2
 * Get step 2 send value : second send
 * This is segment 3
 * The second send result :
 */

這個例子中,第一個send方法的返回值是第二個 yield 表達式的結果值;第二個send方法發送的值成爲了第二個 yield 表達式的結果值;兩個send方法執行的過程中,都對第二個 yield 表達式有操作;那麼到底誰才真正執行了第二個 yield 表達式呢?按照官網的說法,應該是第一個send方法執行了第二個 yield 表達式,因爲第一個send方法的結果值都是第二個 yield 表達之的結果值,那麼也就是說第二個send方法,僅僅是將發送的參數作爲第二個 yield 表達式的結果值,並沒有執行第二個 yield 表達式;如果這樣的話,那麼第一個 yield 表達式是不是就沒有執行,因爲如果第二個send方法不執行第二個 yield 表達式的話,那麼也就是說第一個send方法也沒有執行第一個 yield 表達式,那麼第一個 yield 表達式的就真的沒有執行嗎?

EXP4

function task()
{
    echo "This is segment 1\n"; // 這裏是第一程序段,在第一個yield之前;
    $a = yield son_task(1);
    echo "Get step 1 send value : $a\n";
    echo "This is segment 2\n"; // 這裏是第二程序段,在第一個yield之後,在第二個yield之前;
    $b = yield son_task(2);
    echo "Get step 2 send value : $b\n";
    echo "This is segment 3\n"; // 這裏是第三程序段,在第二個yield之後,在第三個yield之前;
    $c = yield son_task(3);
    echo "Get step 3 send value : $c\n";
    echo "This is segment 4\n"; // 這裏是第四程序段,在第三個yield之後;

}

function son_task($step)
{
    echo "This is son_task of step $step\n";
    return "step $step";
}

$task = task();
$sendResult = $task->send('first send');
echo "The first send result : $sendResult\n";
/**
 * 輸出:
 * This is segment 1
 * This is son_task of step 1
 * Get step 1 send value : first send
 * This is segment 2
 * This is son_task of step 2
 * The first send result : step 2
 */

根據上面例子可以得知:第一個send方法,執行了第一個 yield 表達式和第二個 yield 表達式;

EXP5

function task()
{
    echo "This is segment 1\n"; // 這裏是第一程序段,在第一個yield之前;
    $a = yield son_task(1);
    echo "Get step 1 send value : $a\n";
    echo "This is segment 2\n"; // 這裏是第二程序段,在第一個yield之後,在第二個yield之前;
    $b = yield son_task(2);
    echo "Get step 2 send value : $b\n";
    echo "This is segment 3\n"; // 這裏是第三程序段,在第二個yield之後,在第三個yield之前;
    $c = yield son_task(3);
    echo "Get step 3 send value : $c\n";
    echo "This is segment 4\n"; // 這裏是第四程序段,在第三個yield之後;

}

function son_task($step)
{
    echo "This is son_task of step $step\n";
    return "step $step";
}

$task = task();
$sendResult = $task->send('first send');
echo "The first send result : $sendResult\n";
$sendResult = $task->send('second send');
echo "The second send result : $sendResult\n";
/**
 * 輸出:
 * This is segment 1
 * This is son_task of step 1
 * Get step 1 send value : first send
 * This is segment 2
 * This is son_task of step 2
 * The first send result : step 2
 * Get step 2 send value : second send
 * This is segment 3
 * This is son_task of step 3
 * The second send result : step 3
 */

通過對比EXP4和EXP5得知,第二個send方法僅僅是運行了第三個 yield 表達式,並沒有運行第二個 yield 表達式;
這裏也可以得知,當第一次運營send方法的時候,官網裏面的特殊說明:

如果當前生成器不在yield表達式,它會先自動運行到第一個yield表達式,然後再執行發送$value的操作;因此,沒有必要使用 Generator::next() 來調用PHP生成器(就像在Python中完成的那樣)。

其更準確的含義應該是:如果當前生成器的位置不處於 yield 表達式的話,那麼調用生成器的send方法將會讓生成器自動運行代碼到第一個 yield 表達式,並且執行第一個 yield 表達式;而本次send方法發送的值會作爲當前所處的第一個yield表達式的結果值,send方法會讓生成器繼續執行下一個 yield 表達式,並且將下一個 yield 表達式的結果值作爲send方法的返回值;
也就是說,官網裏面的當前所處於 yield 表達式位置的說法,是當前已經執行過得yield表達式;
如果當前處於一個 yield 表達式位置,那麼使用send方法就僅僅替換下當前位置的yield表達式的結果值,執行下一個 yield 表達式並且將結果值作爲send方法的返回值;

EXP6

我們現在回頭看看上面所有的例子裏面,第一個表達式裏面的結果並沒有返回;也就是說我們通過send方法是無法獲得生成器裏面第一個 yield 表達式的值的;因爲send方法僅僅替換了生成器裏面第一個 yield 表達式的結果值,但是並沒有獲取這個值;所以我們在生成器第一次執行的時候,需要使用current方法來獲取第一個 yield 表達式的值;

function task()
{
    echo "This is segment 1\n"; // 這裏是第一程序段,在第一個yield之前;
    $a = yield son_task(1);
    echo "Get step 1 send value : $a\n";
    echo "This is segment 2\n"; // 這裏是第二程序段,在第一個yield之後,在第二個yield之前;
    $b = yield son_task(2);
    echo "Get step 2 send value : $b\n";
    echo "This is segment 3\n"; // 這裏是第三程序段,在第二個yield之後,在第三個yield之前;
    $c = yield son_task(3);
    echo "Get step 3 send value : $c\n";
    echo "This is segment 4\n"; // 這裏是第四程序段,在第三個yield之後;

}

function son_task($step)
{
    echo "This is son_task of step $step\n";
    return "step $step";
}

$task = task();
$current = $task->current();
echo "This first yield value : $current\n";
/**
 * 輸出:
 * This is segment 1
 * This is son_task of step 1
 * This first yield value : step 1
 */

同樣對於current方法是不是執行了當前的yield,我們也有疑問,於是:

EXP7

function task()
{
    echo "This is segment 1\n"; // 這裏是第一程序段,在第一個yield之前;
    $a = yield son_task(1);
    echo "Get step 1 send value : $a\n";
    echo "This is segment 2\n"; // 這裏是第二程序段,在第一個yield之後,在第二個yield之前;
    $b = yield son_task(2);
    echo "Get step 2 send value : $b\n";
    echo "This is segment 3\n"; // 這裏是第三程序段,在第二個yield之後,在第三個yield之前;
    $c = yield son_task(3);
    echo "Get step 3 send value : $c\n";
    echo "This is segment 4\n"; // 這裏是第四程序段,在第三個yield之後;

}

function son_task($step)
{
    echo "This is son_task of step $step\n";
    return "step $step";
}

$task = task();
$sendResult = $task->send('first send');
echo "The first send result : $sendResult\n";
$current = $task->current();
echo "This current value : $current\n";
/**
 * 輸出:
 * This is segment 1
 * This is son_task of step 1
 * Get step 1 send value : first send
 * This is segment 2
 * This is son_task of step 2
 * The first send result : step 2
 * This current value : step 2
 */

對比EXP6和EXP7,發現current在當生成器當前位置不在yield的時候,也會自動執行到第一個yield位置,並且執行它,這個時候想到了鳥哥說的在生成迭代對象的時候已經隱含地執行了rewind操作;但是經過實驗發現,在生成生成器的時候並沒有執行生成器裏面操作,而是在使用了current,send方法的時候(推測next等方法也可以)自動執行生成器裏面的代碼到第一個yield位置;
那麼既然都有這麼多EXP了,也不差這一個了;

EXP8

function task()
{
    echo "This is segment 1\n"; // 這裏是第一程序段,在第一個yield之前;
    $a = yield son_task(1);
    echo "Get step 1 send value : $a\n";
    echo "This is segment 2\n"; // 這裏是第二程序段,在第一個yield之後,在第二個yield之前;
    $b = yield son_task(2);
    echo "Get step 2 send value : $b\n";
    echo "This is segment 3\n"; // 這裏是第三程序段,在第二個yield之後,在第三個yield之前;
    $c = yield son_task(3);
    echo "Get step 3 send value : $c\n";
    echo "This is segment 4\n"; // 這裏是第四程序段,在第三個yield之後;

}

function son_task($step)
{
    echo "This is son_task of step $step\n";
    return "step $step";
}

$task = task();
$task->rewind();
/**
 * 輸出:
 * This is segment 1
 * This is son_task of step 1
 */

現在可以得知,rewind方法就是將生成器執行到第一個yield表達式位置,並且執行第一個yield表達式的操作;
將EXP8和EXP2對比得知,在生成生成器的時候,並沒有執行rewind操作;而是在調用send等方法的時候,纔會自動的執行一次rewind方法;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章