Generator::send是如何工作的?

6
通常我不会对语言结构感到困惑,但我无法理解这里正在发生什么。
<?php

function action() {
    for($i=0; $i<10; ++$i) {
        $ans = (yield expensive($i));
        echo "action $ans\n";
    }
}

function expensive($i) {
    return $i*2;
}

$gen = action();
foreach($gen as $x) {
    echo "loop $x\n";
    $gen->send($x);
}

输出:

loop 0
action 0
action 
loop 4
action 4
action 
loop 8
action 8
action 
loop 12
action 12
action 
loop 16
action 16
action 

我的循环每两次迭代会跳过一次,有时会周期性地得到NULL$ans。怎么回事?我以为$ans会收到$gen->send的结果,如果在下一个yield之前没有发送任何东西,那么$ans就是空的,但我每次迭代都发送了一些东西,所以这里到底发生了什么?

尝试倒置顺序。我认为(不确定),你需要在生成器内的回显中使用它之前先发送它。 - apokryfos
@apokryfos 现在要倒转什么顺序?在定义 $ans 之前我无法打印它。也许是 foreach 引起的问题... - mpen
我是想说 $gen->send($x); echo "loop $x\n";,并将其与当前的 echo -> send 组合进行比较。 - apokryfos
2个回答

4
这是一个文档问题。这是一个PHP开发人员在错误报告中写的内容:
“next()”和“send()”都会推进生成器。这就是生成器的工作方式。使用“next()”,无论是显式还是隐式地使用,都意味着没有办法通过yield将值传回,因此代码将获得null——就像尝试从不返回任何内容的函数获取返回值时一样。
换句话说,您不能在foreach内部使用“send()”并期望有意义的结果。

实际的foreach调用在每次迭代之后调用next()

/** @return Generator */
function action() {
    for ($i = 0; $i < 5; $i += 1) {
        $answer = (yield $i * 2);
        echo "received: $answer\n";
    }
}


$gen = action();
while ($gen->valid()) {
    $x = $gen->current();
    echo "sending $x\n";
    $gen->send($x);
    $gen->next();
}

现在我们添加了它,代码又开始表现异常:

sending 0
received: 0
received: 
sending 4
received: 4
received: 
sending 8
received: 8

如果我们删除有问题的next(),那么代码将按照预期工作。
$gen = action();
while ($gen->valid()) {
    $x = $gen->current();
    echo "sending $x\n";
    $gen->send($x);
    //$gen->next();
}

输出:

sending 0
received: 0
sending 2
received: 2
sending 4
received: 4
sending 6
received: 6
sending 8
received: 8

听起来像是一个bug。连HHVM都出现了致命错误。

1
我认为“foreach”循环正在搞砸事情。当foreach循环开始时,会创建一个迭代器,我猜它无法处理我向生成器注入新内容的事实。
这个:
<?php

/**
 * @return Generator
 */
function action() {
    for($i=0; $i<10; ++$i) {
        $ans = (yield expensive($i));
        echo "action $ans\n";
    }
}

function expensive($i) {
    return $i*2;
}

$gen = action();
while($gen->valid()) {
    $x = $gen->current();
    echo "loop $x\n";
    $gen->send($x);
}

打印出我所期望的内容:

loop 0
action 0
loop 2
action 2
loop 4
action 4
loop 6
action 6
loop 8
action 8
loop 10
action 10
loop 12
action 12
loop 14
action 14
loop 16
action 16
loop 18
action 18

如果您在同一循环中发送超过一次,则情况会再次变得奇怪:

<?php

/**
 * @return Generator
 */
function action() {
    for($i=0; $i<10; ++$i) {
        $ans = (yield expensive($i));
        echo "action $ans\n";
    }
}

function expensive($i) {
    echo "expensive $i\n";
    return $i;
}

$gen = action();
while($gen->valid()) {
    $x = $gen->current();
    echo "loop $x\n";
    $gen->send($x);
    $gen->send($x);
}

输出:

expensive 0
loop 0
action 0
expensive 1
action 0
expensive 2
loop 2
action 2
expensive 3
action 2
expensive 4
loop 4
action 4
expensive 5
action 4
expensive 6
loop 6
action 6
expensive 7
action 6
expensive 8
loop 8
action 8
expensive 9
action 8

我认为这里发生的情况是send导致action在每个while迭代中迭代两次。如果我们删除两个sends(),那么我们会陷入无限循环。因此,send()推进了迭代器,而current()没有。我认为这也解释了foreach循环的情况 - foreachsend()都推进了迭代器,这就是为什么每隔一个结果被跳过的原因!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接