How does Generator :: send work?

Usually they donโ€™t confuse me in language constructions, but I canโ€™t make the heads or tails of what is happening 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); } 

Print

 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 second iteration of my loop is skipped, and I periodically get NULL for $ans . What??

I thought $ans would get the result of $gen->send , and if I didnโ€™t send anything before the next yield , then $ans will be null, but I always send something at each iteration, so what happens here?

+5
source share
2 answers

This is a problem with the documentation. Here is what the PHP developer wrote in the error report :

next() and send() both advance the generator. This is how generators work. Using next() , explicitly or implicitly, means that there is no way to pass the value back through profitability, and therefore the code will get zero - just like when trying to get the return value from a function that returns nothing.

In other words, you cannot use send() inside 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 have added it, the code starts again incorrectly erroneously:

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

If we remove the offensive next() , the code works as expected.

 $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 mistake to me. Even HHVM fails with a fatal error.

+2
source

I think "foreach" is a mess. When the foreach loop starts, an iterator is created, and I think it cannot handle the fact that I inject new things into the generator.

It:

 <?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 out what I 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 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); } 

Print

 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 happens here is that send invokes the action iteration twice for each while iteration. If we remove the two sends() , then we are stuck in an infinite loop. So ... send() promotes the iterator, but current() does not. And I think this explains what happens with the foreach - both foreach and send() promoted an iterator, so every other result was skipped!

+1
source

All Articles