azjezz / psl

📚 PHP Standard Library - a modern, consistent, centralized, well-typed, non-blocking set of APIs for PHP programmers
MIT License
1.19k stars 68 forks source link

Dict\take() not playing nice with generators #216

Open veewee opened 3 years ago

veewee commented 3 years ago

Describe the bug

When calling Dict\take() and Dict\take_while() twice on an iterator, you receive a fatal exception.

More info, see https://github.com/azjezz/psl/pull/215.

To Reproduce

use Psl\Dict;

$gen = (function() {
    $i = 0;
    while (true) {
        yield ++$i;
    }
})();

// Take example:
$list1 = Dict\take($gen, 3);
$list2 = Dict\take($gen, 3);

// Take while example:
$list1 = Dict\take_while($gen, fn ($i) => $i <= 3);
$list2 = Dict\take_while($gen, fn ($i) => $i <= 6);

Result:

Fatal error: Uncaught Exception: Cannot rewind a generator that was already run in src/Psl/Dict/slice.php on line 42 Exception: Cannot rewind a generator that was already run in src/Psl/Dict/slice.php on line 42

or

Result:

Fatal error: Uncaught Exception: Cannot rewind a generator that was already run in src/Psl/Dict/take_while.php on line 29 Exception: Cannot rewind a generator that was already run in src/Psl/Dict/take_while.php on line 29

Expected behavior It should list:

Additional context

Handling take like this does the trick:

function take (Generator $stream, int $n) {
    $res = [];
    for ($i = 0; $i < $n; $i++) {
        if (!$stream->valid()) {
            break;
        }
        $res[] = $stream->current();
        $stream->next();
    }
    return $res;
};
azjezz commented 2 years ago

fixing this just for take/drop functions might be a good idea, but we have to evaluate the performance impact, thankfully, we now have benchmarks, so we can spot massive performance drops easily.