rfc:generator-delegation

This is an old revision of the document!


PHP RFC: Generator Delegation

Abstract

This RFC proposes new yield * <expr> syntax allowing Generator functions to delegate operation to Traversable objects and arrays. The proposed syntax allows the factoring of yield statements into smaller conceptual units in the same way that discrete class methods simplify object-oriented code. The proposal is conceptually related to and requires functionality proposed by the forerunning Generator Return Expressions RFC.

Proposal

The following new syntax is allowed in the body of generator functions:

    yield * <expr>

In the above code <expr> is any expression that evaluates to a Traversable object (or array). This traversable is advanced until no longer valid, during which time it sends/receives values directly to/from the delegating generator's caller. If the <expr> traversable is a Generator it is considered a subgenerator whose eventual return value is returned to the delegating generator as the result of the origin yield * expression.

Prosaically

  • Each value yielded by the traversable is passed directly to the delegating generator's caller.
  • Each value sent to the delegating generator's send() method is passed to the subgenerator's send() method. If the delegate traversable is not a generator any sent values are ignored as non-generator traversables have no capacity to receive such values.
  • Exceptions thrown by traversable/subgenerator advancement are propagated up the chain to the delegating generator.
  • Upon traversable completion null is returned to the delegating generator if the traversable is NOT a generator. If the traversable is a generator (subgenerator) then the its return value is sent to the delegating generator as the value of the yield * expression.

Formally

The proposed syntax

$g = function() {
    return yield * <expr>;
};

is equivalent to

$g = function() {
    $iter = <expr>;
    $isSubgenerator = $iter instanceof Generator;
    $received = null;
    $send = true;
 
    while ($iter->valid()) {
        if ($isSubgenerator) {
            $next = $send ? $iter->send($received) : $iter->throw($received);
        } else {
            $next = $iter->current();
            $iter->next();
        }
        try {
            $received = yield $next;
            $send = true;
        } catch (Exception $e) {
            if ($isSubgenerator) {
                $received = $e;
                $send = false;
            } else {
                $throw $e;
            }
        }
    }
 
    return $isSubgenerator ? $iter->getReturn() : null;
};

Terminology

  • A “delegating generator” is a Generator in which the yield * <expr> syntax appears.
  • A “subgenerator” is a Generator used in the <expr> portion of the yield * <expr> syntax.

Examples

Delegating to another generator (subgenerator)

<?php
 
function g1() {
  yield 2;
  yield 3;
  yield 4;
}
 
function g2() {
  yield 1;
  yield * g1();
  yield 5;
}
 
$g = g2();
foreach ($g as $yielded) {
    var_dump($yielded);
}
 
/*
int(1)
int(2)
int(3)
int(4)
int(5)
*/

Delegating to non-generator traversables

<?php
 
function g() {
  yield 1;
  yield * [2, 3, 4];
  yield 5;
}
 
$g = g();
foreach ($g as $yielded) {
    var_dump($yielded);
}
 
/*
int(1)
int(2)
int(3)
int(4)
int(5)
*/

The yield* expression value

<?php
 
function g1() {
  yield 2;
  yield 3;
  return 42;
}
 
function g2() {
  yield 1;
  $g1result = yield * g1();
  yield 4;
  return $g1result;
}
 
$g = g2();
foreach ($g as $yielded) {
    var_dump($yielded);
}
var_dump($g->getReturn());
 
/*
int(1)
int(2)
int(3)
int(4)
int(42)
*/

Rationale

@TODO: Refactoring and readability

@TODO: Generators as lightweight threads

Open Issues

  • Is an alternative syntax (e.g. yield from or yield use) preferable?
  • Should sending a value to the delegating generator via Generator::send() result in an exception if the delegate traversable is not a generator (and therefore cannot receive values)?
  • Can array expressions be allowed in yield * <expr> as they are not internally considered Traversable?
  • Should manually advancing a subgenerator outside the context of the delegating generator result in an error? Because PHP generators are stateful object instances the potential exists for manual iteration of a subgenerator currently in-use via yield *. Manual subgenerator advancement may not serve any valid use-case and disallowing this behavior might simplify the implementation.
  • Should a “shared” subgenerator that is already complete return its completed return value to the delegating generator? i.e. should the following code result in null or 42?
function sub() {
    yield 1;
    return 42;
}
function delegator(Generator $shared) {
    return yield * $shared;
}
 
$shared = sub();
while($shared->valid()) { $shared->next(); }
$delegator = delegator($shared);
while($delegator->valid()) { $delegator->next(); }
 
// null or 42?
$result = $delegator->getReturn();

Other Languages

Other popular dynamic languages currently support variants of the proposed syntax ...

Python

Python 3.3 generators support yield from:

>>> def g(x):
...     yield from range(x, 0, -1)
...     yield from range(x)
...
>>> list(g(5))
[5, 4, 3, 2, 1, 0, 1, 2, 3, 4]

JavaScript

Javascript ES6 generators support the yield * syntax:

function* g1() {
  yield 2;
  yield 3;
  yield 4;
}
 
function* g2() {
  yield 1;
  yield* g1();
  yield 5;
}
 
var iterator = g2();
 
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: 4, done: false }
console.log(iterator.next()); // { value: 5, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

Backward Incompatible Changes

None

Proposed PHP Version(s)

PHP7

Unaffected PHP Functionality

Existing generator semantics are unaffected.

Proposed Voting Choices

Two voting choices are proposed:

1. YES, implement the yield * syntax allowing generator delegation in PHP 7

2. NO, do not modify existing Generator behavior

A 2/3 “Yes” vote is required to implement this proposal.

Patches and Tests

There is no patch at this time. This section will be updated in the coming days once a “final” implementation is ready.

Implementation

TBD

References

Rejected Features

TBD

rfc/generator-delegation.1425312022.txt.gz · Last modified: 2017/09/22 13:28 (external edit)