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方法;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值