How to yield empty generator?

2019-02-16 15:09发布

I have a method which takes a generator plus some additional parameters and yields a new generator:

function merge(\Generator $carry, array $additional)
{
    foreach ( $carry as $item ) {
        yield $item;
    }
    foreach ( $additional as $item ) {
        yield $item;
    }
}

The usual use case for this function is similar to this:

function source()
{
    for ( $i = 0; $i < 3; $i++ ) {
        yield $i;
    }
}

foreach ( merge(source(), [4, 5]) as $item ) {
    var_dump($item);
}

But the problem is that sometimes I need to pass empty source to the merge method. Ideally I would like to be able to do something like this:

merge(\Generator::getEmpty(), [4, 5]);

Which is exactly how I would do in C# (there is a IEnumerable<T>.Empty property). But I don't see any kind of empty generator in the manual.

I've managed to work around this (for now) by using this function:

function sourceEmpty()
{
    if ( false ) {
        yield;
    }
}

And this works. The code:

foreach ( merge(sourceEmpty(), [4, 5]) as $item ) {
    var_dump($item);
}

correctly outputs:

int(4)
int(5)

But this is obviously not an ideal solution. What would be the proper way of passing an empty generator to the merge method?

3条回答
我想做一个坏孩纸
2楼-- · 2019-02-16 15:38

I've found the solution:

Since \Generator extends \Iterator I can just change the method signature to this:

function merge(\Iterator $carry, array $additional) 
{
    // ...

This is input covariance thus it would break backward compatibility, but only if someone did extend the merge method. Any invocations will still work.

Now I can invoke the method with PHP's native EmtpyIterator:

merge(new \EmptyIterator, [4, 5]);

And the usual generator also works:

merge(source(), [4, 5])
查看更多
Juvenile、少年°
3楼-- · 2019-02-16 15:50

Bit late, but needed an empty generator myself, and realized creating one is actually quite easy...

function empty_generator(): Generator
{
    yield from [];
}

Don't know if that's better than using the EmptyIterator, but this way you get exactly the same type as non-empty generators at least.

查看更多
做个烂人
4楼-- · 2019-02-16 15:53

As explained in the official docs, you can create an in-line Generator instance, by using yield in an expression:

$empty = (yield);

That should work, but when I tried using that, I got a fatal error (yield expression can only be used in a function). Using null didn't help either:

$empty = (yield null); //error

So I guess you're stuck with the sourceEmpty function... it was the only thing I found that works... note that it will create a null value in the array you're iterating.
All the code was tested on PHP 5.5.9, BTW

The best fix I can come up with (seeing as compatibility is an issue) would be to make both arguments optional:

function merge(\Generator $carry = null, array $additional = array())
{
    if ($carry)
        foreach ($carry as $item)
            yield $item;
    foreach ($additional as $item)
        yield $item;
}
foreach(merge(null, [1,2]) as $item)
    var_dump($item);

This way, existing code won't brake, and instead of constructing an empty generator, passing null will work just fine, too.

查看更多
登录 后发表回答