Usually I'm not confused about language constructs, but I can't make heads or tails of what's going on here.
<?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);
}
Prints:
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
So every 2nd iteration of my loop is skipped, and I'm getting NULL
for $ans
periodically. What??
I thought $ans
would receive the result of $gen->send
, and if I didn't send anything before the next yield
, then $ans
would be null, but I always send something on each iteration, so what's is going on here?
It's a documentation problem. Here's what a PHP developer wrote on a bug report:
next()
andsend()
both advance the generator. That's how generators work. Usingnext()
, explicitly or implicitly, means there's no way to pass a value back through the yield and thus the code will get null - just like when trying to get the return value from a function that doesn't return anything.
In other words, you can't use send()
inside a foreach
and expect meaningful results.
Actual foreach
call 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();
}
Now that we added it, the code starts to misbehave again:
sending 0
received: 0
received:
sending 4
received: 4
received:
sending 8
received: 8
If we remove the offending next()
the code works as one can expect.
$gen = action();
while ($gen->valid()) {
$x = $gen->current();
echo "sending $x\n";
$gen->send($x);
//$gen->next();
}
Outputs:
sending 0
received: 0
sending 2
received: 2
sending 4
received: 4
sending 6
received: 6
sending 8
received: 8
Sounds like a bug for me. Even HHVM fails with a fatal error.
I think the 'foreach' is messing things up. When the foreach loop starts, an iterator is created, and I guess it can't handle the fact that I'm injecting new things into the generator.
This:
<?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);
}
Prints what I'd expect:
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
Things get weird again though if you send
more than once per loop:
<?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);
}
Prints:
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
I think what's happening here is that send
is causing action
to iterate twice for every one while
iteration. If we remove the two sends()
then we get stuck in an infinite loop. So... send()
is advancing the iterator, whereas current()
does not. And I think this explains what was going on with the foreach
loop too -- both the foreach
and send()
were advancing the iterator, which is why every other result was being skipped!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With