Skip to content

Instantly share code, notes, and snippets.

@pcrov
Last active March 4, 2018 19:59
Show Gist options
  • Save pcrov/d3e7048b4d002fbe71aeb91cb212361c to your computer and use it in GitHub Desktop.
Save pcrov/d3e7048b4d002fbe71aeb91cb212361c to your computer and use it in GitHub Desktop.

PHP RFC: Callable Generators

Introduction

Generators reduce the boilerplate necessary to create iterators, this proposal aims to do the same for using generators outside of foreach.

Ultimately this is sugar that allows you to take any generator and simply call it to get the next value.

Proposal

Make generators callable such that each call returns the next yielded value. If a generator is given an argument when called it will be the equivalent of calling send. Without an argument it will be the equivalent of calling current if the generator has not yet yielded a value, or next then current if it has.

Generators would pass type checks for callable, including is_callable().

Rationale

In addition to reducing boilerplate this would mean you don't need to keep track of whether the generator has already yielded a value in order to decide whether to call current on it, or next then current. Another way to handle this is to always call next following current, but you don't always want to immediately advance a generator through a potentially expensive operation, plus it precludes sending should the need arise.

While it's possible to create an object in userland that behaves like this, it couldn't be a generator as Generator is final. Plus it wouldn't help with pre-existing generators - if you're wrapping a given generator there's no way to tell whether it's yielded its first value already, and hence whether to call next before current.

Above all though it is sugar.

Example

function countdown() {
    yield 3;
    yield 2;
    yield 1;
}
$gen = countdown();
$gen(); // 3
$gen(); // 2
$gen(); // 1
$gen(); // NULL

would be equivalent to:

$gen->current(); // 3
$gen->next();
$gen->current(); // 2
$gen->next();
$gen->current(); // 1
$gen->next();
$gen->current(); // NULL

Open Question

What to do when a return expression is reached?

function harumph() {
    yield "Good day, sir!";
    return "I SAID GOOD DAY, SIR!";
}

Option A

It behaves as calling current - returns null. Further calls remain the same, returning null.

$gen = harumph();
$gen(); // Good day, sir!
$gen(); // NULL

Option B

It behaves as calling getReturn - returning the value of the expression. Further calls remain the same, returning the value of the expression.

A benefit of this is that if you're using this simplified interface you're probably not guarding with valid everywhere, and this would let you easily specify your own end-of-generator marker if your generator can otherwise yield null.

$gen = harumph();
$gen(); // Good day, sir!
$gen(); // I SAID GOOD DAY, SIR!

Backward Incompatible Changes

None. The existing interface remains and can be used in conjunction with this.

Unless there's some bit of code out there that specifically relies on generators not being callable, in which case yeah that breaks.

Proposed PHP Version(s)

7.next

Future Scope

It may or may not be desirable to extend this to iterators in general. My name's Paul and that's between y'all.

Voting Choices

A simple yes or no, requiring a 2/3 majority.

Patches and Tests

Looking for volunteers.

Implementation

Somebody? Anybody?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment