Rowan Collins wrote on 02/03/2015 14:44: > Could you explain a bit more about how the generator return > functionality is necessary for this? It seems to me like it's still just > a "nice to have", and that the main functionality - delegating from one > generator to another - is completely separate. Or is there some common > use case that only makes sense if you combine the two features? >
This is a good question and it's 100% justified because I haven't fully addressed it in the body of the RFC. You'll notice there are a couple of @TODO notes still outstanding. So ... I'll try to flesh some of that out now and then copy/paste some of what follows over into the RFC body :) There are two main reasons why return value access is crucial for generator delegation: 1. Refactoring/readability 2. Generators as lightweight "threads" The primary impetus for return values in generator delegations is the same reason why we need return values in class methods: to break up functionality into smaller conceptual units. Imagine a class method where no return value was possible. Yes, we *could* store the result in an instance property and retrieve it from the context of the calling code after execution has completed but this would be somewhat nonsensical (not to mention this is exactly why return values exist). Now also recognize that in functional contexts we don't have this extra state from an object instance. If we don't actually return the result of the computation there's no other way to access it. Consider this simple example that assumes a coroutine() function which knows how to resolve yielded async primitives (usually Promise instances) and resolve its own promise with the eventual generator result: function myGeneratorFunction($foo) { // ... do some stuff with $foo ... $bar = yield * factoredAsyncComputation1($foo); return $bar; } function factoredAsyncComputation($foo) { // ... lots of logic here we want to // ... shift out of the top-level function // yield ...; $bar = yield ...; return $bar + 42; } $promise = coroutine(myGeneratorFunction(1)); When using generators in this way it's reasonable to split asynchronous/concurrent functionality across many functions just like we might with serial code. In such cases it's helpful to call subgenerators as though they were ordinary synchronous functions (passing in parameters and receiving a returned value). Essentially this allows userland code to use `yield` statements as an analog for the `async` keyword found in C# without any changes to the language at all (awesome). Without being able to return values from generators we'd be unable to use generator delegation to arrive at a result from delegated computations. This is also shown in the formal equivalency section of the proposal linked here: https://wiki.php.net/rfc/generator-delegation#formally Absent the ability to call Generator::getReturn() the expanded generator delegation code would have no way to return a result from the subgenerator unless you introduce/manage a form of state yourself. With no return value we're essentially stuck in a multitasking environment where we can only offload "background" work without having any way to tell what the result of that concurrent processing turned out to be. Now, one might argue that we shouldn't need to expose `Generator::getReturn()` as we can simply return the results directly to the delegation expression in the parent generator. The problem with this argument is that without the ability to access the return value from individual subgenerators outside the context of the delegating generator we're left unable to do things like write unit tests for individual generator functions to verify that they work as expected. Is it unfortunate that we have to use a stateful "object" with actual public methods to represent pausable functions in userland? Maybe. But I don't (personally) see this as a sound reason to argue that generator functions shouldn't behave like traditional serial functions. Beyond this: other languages (python, javascript) also expose the ability to return values from generator functions as mentioned. > The reason I ask is that "yield from" (or whatever syntax) sounds > useful, but I'm not that keen on allowing "return" in generators, > because I can see it further muddying the water between functions and > generators: when you call the following "function", it doesn't actually > return 1 as it would appear at a glance, because the existence of a > yield statement elsewhere in the body magically makes it return a > Generator object instead: > > function foo() { > // potentially many lines of code > yield 1; > // potentially many more lines of code > return 1; > } > This confusion is not the result of a fundamental difference between functions and generators; a generator is a function is a function is a function. A generator is simply a function promoted to have traversable characteristics. It should not be thought of as a traversable object that happens to be statically instantiated via function call. It just so happens that to make generators work cleanly in PHP we need to create and return an iterable "placeholder" Generator object to encapsulate the necessary state for pausing and resumption. This is unfortunate because it leads to the kind of confusion above, but it's simply the only way to expose this functionality. If it weren't a function it wouldn't be a generator ... it would just be some traversable thing. > I would personally have preferred generators to have used a distinct > keyword rather than just looking like functions, but since that ship has > sailed, making them look even more like functions worries me. Is there > perhaps some other syntax that could be used for this "generator result > value"? > This is the fundamental mental block that most people have when they try to wrap their heads around cooperative multitasking via coroutines. We need to understand Generators are not iterators. They are functions with the added capacity that they can be paused and resumed. The function keyword is the only thing that makes sense here. Hopefully that addresses some of your questions? If not, please ask again and I'll try to do a better job. Thanks for the feedback, Daniel