rfc:generator-delegation
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revisionNext revisionBoth sides next revision | ||
rfc:generator-delegation [2015/03/02 16:00] – rdlowrey | rfc:generator-delegation [2015/03/15 19:31] – changed vote heading rdlowrey | ||
---|---|---|---|
Line 1: | Line 1: | ||
====== PHP RFC: Generator Delegation ====== | ====== PHP RFC: Generator Delegation ====== | ||
- | * Version: 0.1 | + | * Version: 0.2.0 |
* Date: 2015-03-01 | * Date: 2015-03-01 | ||
- | * Authors: Daniel Lowrey < | + | * Author: Daniel Lowrey < |
- | * Status: | + | * Contributors: |
+ | * Status: | ||
* First Published at: http:// | * First Published at: http:// | ||
====== Abstract ====== | ====== Abstract ====== | ||
- | This RFC proposes new '' | + | This RFC proposes new '' |
====== Proposal ====== | ====== Proposal ====== | ||
Line 15: | Line 16: | ||
<code php> | <code php> | ||
- | yield * < | + | yield from < |
</ | </ | ||
- | In the above code ''< | + | In the above code ''< |
traversable is advanced until no longer valid, during which time it sends/ | traversable is advanced until no longer valid, during which time it sends/ | ||
to/from the delegating generator' | to/from the delegating generator' | ||
considered a subgenerator whose eventual return value is returned to the delegating generator as the | considered a subgenerator whose eventual return value is returned to the delegating generator as the | ||
- | result of the origin | + | result of the originating |
- | === Prosaically === | + | ==== Terminology ==== |
+ | |||
+ | * A " | ||
+ | |||
+ | * A " | ||
+ | |||
+ | ==== Prosaically | ||
* Each value yielded by the traversable is passed directly to the delegating generator' | * Each value yielded by the traversable is passed directly to the delegating generator' | ||
Line 32: | Line 39: | ||
* Exceptions thrown by traversable/ | * Exceptions thrown by traversable/ | ||
- | * Upon traversable completion '' | + | * Upon traversable completion '' |
- | === Formally === | + | ==== Formally |
The proposed syntax | The proposed syntax | ||
Line 40: | Line 47: | ||
<code php> | <code php> | ||
$g = function() { | $g = function() { | ||
- | return yield * < | + | return yield from < |
}; | }; | ||
</ | </ | ||
Line 77: | Line 84: | ||
</ | </ | ||
- | === Terminology === | ||
- | * A " | + | ===== Rationale ===== |
+ | |||
+ | A major impetus for generator | ||
+ | the same guiding principle employed when returning values from discrete class methods. Imagine | ||
+ | class method from which no return value is possible. In such a scenario we //could// store the result | ||
+ | in an instance property and subsequently retrieve it from the context of the calling code. However, | ||
+ | this kind of superfluous state quickly becomes difficult to reason about. | ||
+ | |||
+ | Additionally, | ||
+ | to store results. In the absence of the standard input-output paradigm there's no way to access the | ||
+ | eventual result of a generator's pausable computations. Of course, it //is// possible to work around this | ||
+ | suboptimal situation with references and closure | ||
+ | instantly eliminated when Generator functions are allowed to return expressions. Return values minimize | ||
+ | cognitive overhead | ||
+ | its eventual result. | ||
+ | |||
+ | Generator delegation -- at its heart -- is nothing more than the application of standard factoring practices | ||
+ | to allow the decomposition of complex operations into smaller cohesive units. | ||
+ | |||
+ | ==== Use-Case: Factored Generator Computations ==== | ||
+ | |||
+ | In this simple example we demonstrate | ||
+ | into multiple discrete generators. Callers of '' | ||
+ | individual yielded values came (nor should they). Instead, they simply iterate over the yielded values | ||
+ | awaiting the generator function' | ||
+ | |||
+ | <code php> | ||
+ | function myGeneratorFunction($foo) { | ||
+ | // ... do some stuff with $foo ... | ||
+ | $bar = yield from factoredComputation1($foo); | ||
+ | // ... do some stuff with $bar ... | ||
+ | $baz = yield from factoredComputation2($bar); | ||
+ | |||
+ | return $baz; | ||
+ | } | ||
+ | function factoredComputation1($foo) { | ||
+ | yield ...; // pseudo-code (something we factored out) | ||
+ | yield ...; // pseudo-code (something we factored out) | ||
+ | return | ||
+ | } | ||
+ | function factoredComputation2($bar) { | ||
+ | yield ...; // pseudo-code (something we factored out) | ||
+ | yield ...; // pseudo-code (something we factored out) | ||
+ | return 42; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | |||
+ | ==== Use-Case: Generators as Lightweight Threads === | ||
+ | |||
+ | The defining feature of Generator functions is their support for suspending execution | ||
+ | for later resumption. This capability gives applications a mechanism to | ||
+ | implement asynchronous and concurrent architectures even in a traditionally single-threaded language | ||
+ | like PHP. With simple userland task scheduling systems interleaved generators become lightweight | ||
+ | threads of execution for concurrent processing tasks. | ||
+ | |||
+ | In the absence of generator return values, though, applications face an environment where " | ||
+ | tasks can be offloaded without a standardized way to return the eventual result. | ||
+ | This is one reason why this proposal depends on the acceptance of the Generator Return Expressions RFC. | ||
+ | The other reason return values are required stems from the previously discussed refactoring principle. | ||
+ | Specifically: | ||
+ | like ordinary functions. | ||
+ | |||
+ | Using the proposed | ||
+ | |||
+ | '' | ||
+ | |||
+ | can be transformed into a subgenerator delegation of the form | ||
+ | |||
+ | '' | ||
+ | |||
+ | where '' | ||
+ | abstractions without the cognitive overhead often associated with threaded multitasking. In the | ||
+ | above example generator delegation allows the language to do the heavy lifting while the programmer | ||
+ | need only concern herself with the input, '' | ||
+ | |||
+ | In short: generator delegation allows programmers to reason about the behaviour of the concurrent code | ||
+ | simply by thinking of '' | ||
- | | + | **NB:** The actual implementation of coroutine task schedulers |
+ | this document. This RFC focuses only on the language-level machinery needed to make such tools more | ||
+ | feasible in userland. It should be obvious that simply moving code into a generator function will | ||
+ | not somehow make it magically concurrent. | ||
- | ===== Examples ===== | + | ===== Basic Examples ===== |
Delegating to another generator (subgenerator) | Delegating to another generator (subgenerator) | ||
Line 99: | Line 185: | ||
function g2() { | function g2() { | ||
yield 1; | yield 1; | ||
- | yield * g1(); | + | yield from g1(); |
yield 5; | yield 5; | ||
} | } | ||
$g = g2(); | $g = g2(); | ||
+ | foreach ($g as $yielded) { | ||
+ | var_dump($yielded); | ||
+ | } | ||
+ | |||
+ | /* | ||
+ | int(1) | ||
+ | int(2) | ||
+ | int(3) | ||
+ | int(4) | ||
+ | int(5) | ||
+ | */ | ||
+ | </ | ||
+ | |||
+ | Delegating to an array | ||
+ | |||
+ | <code php> | ||
+ | <?php | ||
+ | |||
+ | function g() { | ||
+ | yield 1; | ||
+ | yield from [2, 3, 4]; | ||
+ | yield 5; | ||
+ | } | ||
+ | |||
+ | $g = g(); | ||
foreach ($g as $yielded) { | foreach ($g as $yielded) { | ||
var_dump($yielded); | var_dump($yielded); | ||
Line 124: | Line 235: | ||
function g() { | function g() { | ||
yield 1; | yield 1; | ||
- | yield * [2, 3, 4]; | + | yield from new ArrayIterator([2, 3, 4]); |
yield 5; | yield 5; | ||
} | } | ||
Line 142: | Line 253: | ||
</ | </ | ||
- | The yield* expression value | + | The '' |
<code php> | <code php> | ||
Line 155: | Line 266: | ||
function g2() { | function g2() { | ||
yield 1; | yield 1; | ||
- | $g1result = yield * g1(); | + | $g1result = yield from g1(); |
yield 4; | yield 4; | ||
return $g1result; | return $g1result; | ||
Line 175: | Line 286: | ||
</ | </ | ||
- | ===== Rationale ===== | ||
- | @TODO: Refactoring and readability | ||
- | @TODO: Generators as lightweight threads | + | ===== Selected Implementation Details ===== |
- | ===== Open Issues ===== | + | The delegation implementation builds on the patch submitted as part of the Generator Return Expressions RFC. |
+ | This implementation adds the following new parsing token: | ||
- | * Is an alternative syntax (e.g. '' | + | '' |
- | * Should sending | + | The primary advantage of this approach is the addition of a readable and semantically meaningful syntax |
+ | without reserving a new '' | ||
- | * Can '' | ||
- | * 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 '' | + | ==== Subgenerator Keys ==== |
- | | + | As Generator iteration is always associated with an accompanying key there exists the potential that |
+ | a given delegation may return the same key multiple times from separate individual generators. This | ||
+ | was deemed unproblematic for three primary reasons. | ||
+ | |||
+ | | ||
+ | |||
+ | * If a caller derives semantic meaning from yielded keys the burden is placed on generator value producers to yield keys sensible for problem domain in which they exist. The burden here is not on the language to avoid duplicate keys. | ||
+ | |||
+ | * The '' | ||
+ | |||
+ | |||
+ | ==== Shared Subgenerator Behavior ==== | ||
+ | |||
+ | PHP generator functions are implemented as stateful object instances. Although " | ||
+ | instances does not present any immediately obvious use-cases, such behavior is still supported. Here | ||
+ | we note some of the characteristics of shared generator functions. | ||
+ | |||
+ | If a " | ||
+ | expression | ||
<code php> | <code php> | ||
- | function | + | function |
yield 1; | yield 1; | ||
return 42; | return 42; | ||
} | } | ||
function delegator(Generator $shared) { | function delegator(Generator $shared) { | ||
- | return yield * $shared; | + | return yield from $shared; |
} | } | ||
- | $shared = sub(); | + | $shared = subgenerator(); |
while($shared-> | while($shared-> | ||
$delegator = delegator($shared); | $delegator = delegator($shared); | ||
- | while($delegator-> | ||
- | // null or 42? | + | foreach ($delegator as $value) { |
- | $result = $delegator-> | + | |
+ | } | ||
+ | var_dump($delegator-> | ||
+ | |||
+ | /* | ||
+ | int(42) | ||
+ | // This is our only output because no values are yielded | ||
+ | // from the already-completed shared subgenerator | ||
+ | */ | ||
</ | </ | ||
+ | |||
+ | |||
+ | Manually advancing a shared subgenerator outside the context of the delegating generator will not | ||
+ | result in an error. In code: | ||
+ | |||
+ | <code php> | ||
+ | function subgenerator() { | ||
+ | yield 1; | ||
+ | yield 2; | ||
+ | yield 3; | ||
+ | yield 4; | ||
+ | return 42; | ||
+ | } | ||
+ | function delegator(Generator $shared) { | ||
+ | return yield from $shared; | ||
+ | } | ||
+ | |||
+ | $shared = subgenerator(); | ||
+ | $shared-> | ||
+ | $delegator = delegator($shared); | ||
+ | var_dump($delegator-> | ||
+ | $shared-> | ||
+ | while($delegator-> | ||
+ | var_dump($delegator-> | ||
+ | $delegator-> | ||
+ | } | ||
+ | var_dump($delegator-> | ||
+ | |||
+ | /* | ||
+ | int(2); | ||
+ | int(3); | ||
+ | int(4); | ||
+ | int(42) | ||
+ | */ | ||
+ | </ | ||
+ | |||
+ | ==== Error States ==== | ||
+ | |||
+ | There are two scenarios in which '' | ||
+ | |||
+ | * Using '' | ||
+ | |||
+ | * Using '' | ||
+ | | ||
+ | ===== Rejected Ideas ===== | ||
+ | |||
+ | The original version of this RFC proposed a '' | ||
+ | '' | ||
+ | form was considered a less readable option that the current proposal. | ||
+ | |||
+ | |||
+ | ===== Criticisms ===== | ||
+ | |||
+ | It has been suggested during the discussion phase that a mechanism other than '' | ||
+ | subgenerators to establish the value returned to delegating generators by '' | ||
+ | The following counter-arguments are provided to this criticism: | ||
+ | |||
+ | * Forms other than '' | ||
+ | |||
+ | * '' | ||
+ | |||
+ | * Other popular dynamic languages inhabiting a similar space to PHP implement generator delegation value resolution using the '' | ||
+ | |||
===== Other Languages ===== | ===== Other Languages ===== | ||
Line 217: | Line 415: | ||
**Python** | **Python** | ||
- | Python 3.3 generators support '' | + | Python 3.3 generators support |
<code python> | <code python> | ||
- | >>> | + | >>> |
- | ... yield from range(x, | + | ... tally = 0 |
- | ... | + | ... while 1: |
+ | ... next = yield | ||
+ | ... if next is None: | ||
+ | ... | ||
+ | ... tally += next | ||
... | ... | ||
- | >>> | + | >>> |
- | [5, 4, 3, 2, 1, 0, 1, 2, 3, 4] | + | ... while 1: |
+ | ... tally = yield from accumulate() | ||
+ | ... | ||
+ | ... | ||
+ | >>> | ||
+ | >>> | ||
+ | >>> | ||
+ | >>> | ||
+ | ... | ||
+ | ... | ||
+ | >>> | ||
+ | >>> | ||
+ | ... | ||
+ | ... | ||
+ | >>> | ||
+ | >>> | ||
+ | [6, 10] | ||
</ | </ | ||
**JavaScript** | **JavaScript** | ||
- | Javascript ES6 generators support the '' | + | Javascript ES6 generators support the '' |
<code javascript> | <code javascript> | ||
- | function* | + | function* |
- | yield 2; | + | yield* [1, 2, 3]; |
- | yield 3; | + | |
- | | + | |
} | } | ||
- | function* | + | var result; |
- | | + | |
- | | + | function* |
- | yield 5; | + | |
} | } | ||
- | var iterator = g2(); | + | var iterator = g5(); |
console.log(iterator.next()); | console.log(iterator.next()); | ||
console.log(iterator.next()); | console.log(iterator.next()); | ||
console.log(iterator.next()); | console.log(iterator.next()); | ||
- | console.log(iterator.next()); | + | console.log(iterator.next()); |
- | console.log(iterator.next()); // { value: | + | // g4() returned |
- | console.log(iterator.next()); // { value: undefined, done: true } | + | |
+ | console.log(result); // " | ||
</ | </ | ||
Line 268: | Line 486: | ||
- | ===== Proposed Voting Choices | + | ===== Vote ===== |
- | Two voting choices are proposed: | + | A 2/3 " |
- | 1. **YES**, implement the '' | + | <doodle title=" |
+ | * Yes | ||
+ | * No | ||
+ | </ | ||
- | 2. **NO**, do not modify existing Generator behavior | + | . |
- | A 2/3 " | + | The success of this vote depends on the success of the accompanying [[rfc: |
===== Patches and Tests ===== | ===== Patches and Tests ===== | ||
- | There is no patch at this time. This section will be updated | + | The current patch is considered " |
+ | |||
+ | https:// | ||
+ | |||
+ | The patch was written by Bob Weinand and is based upon the implementation branch written by Nikita Popov | ||
+ | for the [[rfc: | ||
+ | exist in the implementation | ||
+ | implementation and read the test cases to ascertain the full nature of the proposal. | ||
===== Implementation ===== | ===== Implementation ===== | ||
Line 294: | Line 522: | ||
* [[https:// | * [[https:// | ||
- | ===== Rejected Features | + | |
- | TBD | + | ===== Changelog |
+ | |||
+ | * v0.2.0 Moved to '' | ||
+ | * v0.1.0 Initial proposal |
rfc/generator-delegation.txt · Last modified: 2017/09/22 13:28 by 127.0.0.1