Generator :: send如何工作?

时间:2016-06-14 16:18:58

标签: php generator

通常我对语言结构并不感到困惑,但我无法对这里发生的事情做出正面或反面。

<?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之前没有发送任何内容,那么 {{1}将是null,但我总是在每次迭代时发送一些东西,那么这里发生了什么?

2 个答案:

答案 0 :(得分:2)

这是一个文档问题。这就是PHP开发人员wrote on a bug report

  

next()send()都推进了生成器。这就是发电机的工作原理。显式或隐式地使用next()意味着无法通过yield返回值,因此代码将变为null - 就像尝试从不具有&#的函数获取返回值一样39;不要做任何事。

换句话说,您无法在send()内使用foreach并期望获得有意义的结果。

实际foreach来电next() after each iteration

/** @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

对我来说听起来像个错误。 Even HHVM fails with a fatal error.

答案 1 :(得分:1)

我认为&#39; foreach&#39;弄乱了。当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

如果每个循环send不止一次,事情就会变得奇怪:

<?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()都推进了迭代器,这就是为什么每个其他结果都被跳过了!