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

Reply via email to