Hi folks,
I'd like to initiate discussion on a proposal to implement generator
delegation via the following new syntax inside generator functions:
yield * <expr>
The Generator Delegation RFC is available here:
https://wiki.php.net/rfc/generator-delegation
This proposal is conceptually related to (and requires functionality
proposed by) the forerunning Generator Return Expressions RFC here:
https://wiki.php.net/rfc/generator-return-expressions
Thanks for your time,
Daniel
2015-03-02 0:52 GMT+01:00 Daniel Lowrey rdlowrey@php.net:
Hi folks,
I'd like to initiate discussion on a proposal to implement generator
delegation via the following new syntax inside generator functions:yield * <expr>
The Generator Delegation RFC is available here:
https://wiki.php.net/rfc/generator-delegation
This proposal is conceptually related to (and requires functionality
proposed by) the forerunning Generator Return Expressions RFC here:https://wiki.php.net/rfc/generator-return-expressions
Thanks for your time,
Daniel
The proposed syntax has an issue:
<?php
function a () {
echo yield * 3;
}
$a = a();
$a->send(42);
This is currently valid PHP.
Regards, Niklas
The proposed syntax has an issue:
<?php
function a () {
echo yield * 3;
}$a = a();
$a->send(42);This is currently valid PHP.
I've created a test for this, so we don't forget it in future
Am 02.03.2015 um 01:17 schrieb Niklas Keller me@kelunik.com:
2015-03-02 0:52 GMT+01:00 Daniel Lowrey rdlowrey@php.net:
Hi folks,
I'd like to initiate discussion on a proposal to implement generator
delegation via the following new syntax inside generator functions:yield * <expr>
The Generator Delegation RFC is available here:
https://wiki.php.net/rfc/generator-delegation
This proposal is conceptually related to (and requires functionality
proposed by) the forerunning Generator Return Expressions RFC here:https://wiki.php.net/rfc/generator-return-expressions
Thanks for your time,
Daniel
The proposed syntax has an issue:
<?php
function a () {
echo yield * 3;
}$a = a();
$a->send(42);This is currently valid PHP.
Regards, Niklas
Hey,
I think we could use "yield from" as operator here.
Yes, BC. So, I won't make "from" a keyword, but rather specify an own token for "yield from" (T_YIELD_FROM).
That way we have no BC break, except someone tries to yield a constant named "from". I think that's a really minor break … and in case this breaks someones code, he needs to wrap the constant in parenthesis: "yield (from)".
We can have nice syntax here with a minimal break, so, then, why not?
Thanks,
Bob
2015-03-02 11:11 GMT+01:00 Bob Weinand bobwei9@hotmail.com:
Am 02.03.2015 um 01:17 schrieb Niklas Keller me@kelunik.com:
2015-03-02 0:52 GMT+01:00 Daniel Lowrey rdlowrey@php.net:
Hi folks,
I'd like to initiate discussion on a proposal to implement generator
delegation via the following new syntax inside generator functions:yield * <expr>
The Generator Delegation RFC is available here:
https://wiki.php.net/rfc/generator-delegation
This proposal is conceptually related to (and requires functionality
proposed by) the forerunning Generator Return Expressions RFC here:https://wiki.php.net/rfc/generator-return-expressions
Thanks for your time,
Daniel
The proposed syntax has an issue:
<?php
function a () {
echo yield * 3;
}$a = a();
$a->send(42);This is currently valid PHP.
Regards, Niklas
Hey,
I think we could use "yield from" as operator here.
Yes, BC. So, I won't make "from" a keyword, but rather specify an own
token for "yield from" (T_YIELD_FROM).
That way we have no BC break, except someone tries to yield a constant
named "from". I think that's a really minor break … and in case this breaks
someones code, he needs to wrap the constant in parenthesis: "yield (from)".We can have nice syntax here with a minimal break, so, then, why not?
Thanks,
Bob
If those keywords can be combined to one, that would be totally fine.
I think BC breaks are less a problem when the lexer RFC passes, but that
shouldn't lead to a lot more keyboards being added.
So yield from will be totally fine, it's already used in other languages.
Regards, Niklas
Am 02.03.2015 um 11:28 schrieb Niklas Keller me@kelunik.com:
2015-03-02 11:11 GMT+01:00 Bob Weinand <bobwei9@hotmail.com mailto:bobwei9@hotmail.com>:Am 02.03.2015 um 01:17 schrieb Niklas Keller <me@kelunik.com mailto:me@kelunik.com>:
2015-03-02 0:52 GMT+01:00 Daniel Lowrey <rdlowrey@php.net mailto:rdlowrey@php.net>:
Hi folks,
I'd like to initiate discussion on a proposal to implement generator
delegation via the following new syntax inside generator functions:yield * <expr>
The Generator Delegation RFC is available here:
https://wiki.php.net/rfc/generator-delegation https://wiki.php.net/rfc/generator-delegation
This proposal is conceptually related to (and requires functionality
proposed by) the forerunning Generator Return Expressions RFC here:https://wiki.php.net/rfc/generator-return-expressions https://wiki.php.net/rfc/generator-return-expressions
Thanks for your time,
Daniel
The proposed syntax has an issue:
<?php
function a () {
echo yield * 3;
}$a = a();
$a->send(42);http://3v4l.org/n1sGb#v550 http://3v4l.org/n1sGb#v550
This is currently valid PHP.
Regards, Niklas
Hey,
I think we could use "yield from" as operator here.
Yes, BC. So, I won't make "from" a keyword, but rather specify an own token for "yield from" (T_YIELD_FROM).
That way we have no BC break, except someone tries to yield a constant named "from". I think that's a really minor break … and in case this breaks someones code, he needs to wrap the constant in parenthesis: "yield (from)".We can have nice syntax here with a minimal break, so, then, why not?
Thanks,
BobIf those keywords can be combined to one, that would be totally fine.
I think BC breaks are less a problem when the lexer RFC passes, but that shouldn't lead to a lot more keyboards being added.
So yield from will be totally fine, it's already used in other languages.Regards, Niklas
As said, I won't add a "from" keyword. It's just the combination of "yield" and "from" with will be interpreted differently at lexer level.
And yes, that's how e.g. Python calls it too, hence this suggestion.
Bob
Am 02.03.2015 00:52 schrieb "Daniel Lowrey" rdlowrey@php.net:
I'd like to initiate discussion on a proposal to implement generator
delegation via the following new syntax inside generator functions:yield * <expr>
The Generator Delegation RFC is available here:
I like the feature. The syntax is awful. What about "yield use (expr)"? No
new keywords, somewhat gramatical, and currently invalid syntax, as far as
I can tell.
Patrick
Am 02.03.2015 00:52 schrieb "Daniel Lowrey" rdlowrey@php.net:
I'd like to initiate discussion on a proposal to implement generator
delegation via the following new syntax inside generator functions:yield * <expr>
The Generator Delegation RFC is available here:
I like the feature. The syntax is awful. What about "yield use (expr)"?
No new keywords, somewhat gramatical, and currently invalid syntax, as far
as I can tell.
IMO yield use (expr)
is awful. Let's note here the unhelpful nature of
adjectives like "awful" in technical discussions.
That said, I have no special attachment to yield *
. As noted in the RFC,
yield *
is the syntax used in JavaScript generator delegation. I do see
value in using common idioms for existing functionality in other languages.
However, I also appreciate how the Python variation yield from
is both
immediately readable and perhaps easier to understand. Pending any
implementation difficulties I'm indifferent between yield from
and yield *
.
I think the functionality is the important thing to take away here; the
syntax can be whatever folks want it to be.
From initial discussions it seems prudent to modify the Generator
Delegation RFC to use either of:
- yield from <expr>
- yield use <expr>
The yield from
form would add a single T_YIELD_FROM
token (as opposed to
a new "from" reserved word). The yield use
form would reuse existing
keywords. I'd like to get feedback from people on preferences between these
two options before modifying the RFC. If general consensus isn't reached
this can be a voting option, but I prefer a traditional yes/no vote
to ménage à trois.
Thanks again for your participation.
I prefer yield from, because it yields that value from the generator. Yield
use doesn't make any sense to me if I read those words.
Additionally, it's already used by another language.
Regards, Niklas
Daniel Lowrey rdlowrey@php.net schrieb am Mo., 02.03.2015, 17:13:
From initial discussions it seems prudent to modify the Generator
Delegation RFC to use either of:
- yield from <expr>
- yield use <expr>
The
yield from
form would add a singleT_YIELD_FROM
token (as opposed to
a new "from" reserved word). Theyield use
form would reuse existing
keywords. I'd like to get feedback from people on preferences between these
two options before modifying the RFC. If general consensus isn't reached
this can be a voting option, but I prefer a traditional yes/no vote
to ménage à trois.Thanks again for your participation.
Gr, top-posting...
I prefer yield from, because it yields that value from the generator. Yield
use doesn't make any sense to me if I read those words.Additionally, it's already used by another language.
Regards, Niklas
^^ Agreed on all points.
--Larry Garfield
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:
- Refactoring/readability
- 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
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 :)
Hi Daniel,
Thanks for taking the time to clarify. I think I almost understand now...
There are two main reasons why return value access is crucial for
generator delegation:
- Refactoring/readability
- 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.
Well, in a normal generator context, we have two-way communication using
"yield". The case for a distinct final value is one of convenience, until
we get to delegation. With delegation, we can't get data from the
subgenerator's "yield", because it will be automatically delegated. Is that
more or less right?
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.
This part I don't understand. I can't use a generator where I'd normally
use a function, and I can't use a function where I'd use a generator. The
only way I can reason about them in the same way is to say that the
generator is a function whose return value is an instance of something.
That's why being able to specify "return" feels so wrong to me - the return
value of foo() should be what I get when I execute foo().
The fact that it's iterable aside, I can't see what other form a coroutine
could take. In order to be useful, you need to have a pointer to something
with identity, internal state, and behaviour. That sure sounds like an
object, which makes anything which produces that thing sound very like a
constructor. A function has none of those traits.
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.
The fundamental difference is that functions are never instantiated - you
don't initialise a copy of strlen()
then interrogate it for its value,
there is a single function, and the result of calling it is an integer you
can pass to other functions. Coroutines may have the same role as
functions, as units of executable code, but they have fundamentally
different behaviour, so I don't see how it's helpful to fold them
together.
Would it not be clearer to say this?
coroutine foo($bar) { blah(); blah(); yield; etc(); return $bar; }
$foo = new foo(42);
// $foo now points to an instance of the coroutine
You could even have coroutines with no yield statements that way, which the
current syntax doesn't allow.
However, since the existence of the word "yield" is the only thing that
marks a coroutine now, how about using a variant of that for the final
value, e.g. "yield final $foo"? That would remove the perceived ambiguity
between a value being returned in the normal way, and one accessible only
through Generator syntax.
Regards,
Rowan Collins
[IMSoP]
Gr, top-posting...
Sorry, was on mobile. ;-)
However, since the existence of the word "yield" is the only thing that
marks a coroutine now, how about using a variant of that for the final
value, e.g. "yield final $foo"?
What's the final value? The last "yield"ed value or a return?
Just to give you some real world example:
If you're using "return", it would look like that:
public function getSession ($sessionId) {
$result = yield $this->redis->get("session.{$sessionId}")); // We're
waiting here until redis responded.
return json_decode($result);
}
Currently, it looks like this:
public function getSession ($sessionId) {
$result = yield $this->redis->get("session.{$sessionId}"));
yield json_decode($result);
}
Or maybe even that:
public function getSession ($sessionId) {
$result = yield $this->redis->get("session.{$sessionId}"));
yield "return" => json_decode($result);
}
I think having the possibility for a return here, would make that code a
lot easier to read and understand.
Regards, Niklas
Niklas Keller wrote on 03/03/2015 10:55:
Gr, top-posting...
Sorry, was on mobile. ;-)
However, since the existence of the word "yield" is the only thing that marks a coroutine now, how about using a variant of that for the final value, e.g. "yield final $foo"?
What's the final value? The last "yield"ed value or a return?
"yield final" would mark the final result of the coroutine, as opposed
to the intermediate values passed out with a normal "yield".
Just to give you some real world example:
If you're using "return", it would look like that:public function getSession ($sessionId) {
$result = yield $this->redis->get("session.{$sessionId}")); //
We're waiting here until redis responded.
return json_decode($result);
}
My suggestion is simply to change the keyword:
public function getSession ($sessionId) {
$result = yield $this->redis->get("session.{$sessionId}")); //
We're waiting here until redis responded.
yield final json_decode($result);
}
The reasoning being that when you run getSession(42), it doesn't
return the result of json_decode()
, it returns a pointer to the
coroutine's state, which you can resume later.
Actually, I don't think that example makes sense, because JSON gets sent
out at the first yield, and then sent back in, so the caller would look
something like this:
$foo = getSession(42);
$json_data = $foo->current();
$foo->send($json_data);
$decoded = $foo->getReturn();
But never mind, I think we both get the idea.
I understand the desire for a "final result", but I don't like reusing
the word "return", because it's never "returned" as the result of
running the function, it's just made available through some specific
method/syntax.
Regards,
Rowan Collins
[IMSoP]
2015-03-03 12:34 GMT+01:00 Rowan Collins rowan.collins@gmail.com:
Niklas Keller wrote on 03/03/2015 10:55:
Gr, top-posting...
Sorry, was on mobile. ;-)
However, since the existence of the word "yield" is the only thing that
marks a coroutine now, how about using a variant of that for the final
value, e.g. "yield final $foo"?What's the final value? The last "yield"ed value or a return?
"yield final" would mark the final result of the coroutine, as opposed to
the intermediate values passed out with a normal "yield".Just to give you some real world example:
If you're using "return", it would look like that:public function getSession ($sessionId) {
$result = yield $this->redis->get("session.{$sessionId}")); // We're
waiting here until redis responded.
return json_decode($result);
}My suggestion is simply to change the keyword:
public function getSession ($sessionId) {
$result = yield $this->redis->get("session.{$sessionId}")); // We're
waiting here until redis responded.
yield final json_decode($result);
}The reasoning being that when you run getSession(42), it doesn't return
the result ofjson_decode()
, it returns a pointer to the coroutine's state,
which you can resume later.Actually, I don't think that example makes sense, because JSON gets sent
out at the first yield, and then sent back in, so the caller would look
something like this:$foo = getSession(42);
$json_data = $foo->current();
$foo->send($json_data);
$decoded = $foo->getReturn();But never mind, I think we both get the idea.
I understand the desire for a "final result", but I don't like reusing
the word "return", because it's never "returned" as the result of running
the function, it's just made available through some specific method/syntax.Regards,
Rowan Collins
[IMSoP]
$foo = getSession(42);
$json_data = $foo->current();
$foo->send($json_data);
$decoded = $foo->getReturn();
Yes, it doesn't make sense if you call it directly, actually, this an
example from my chat that uses the amp-framework (
https://github.com/amphp/amp), so this isn't called directly.
To me, it isn't obvious that "yield final ..." makes the generator end, but
anyone would know if there's a "return".
Regards, Niklas
Am 03.03.2015 um 12:34 schrieb Rowan Collins rowan.collins@gmail.com:
Niklas Keller wrote on 03/03/2015 10:55:
Gr, top-posting...
Sorry, was on mobile. ;-)
However, since the existence of the word "yield" is the only thing
that
marks a coroutine now, how about using a variant of that for the final
value, e.g. "yield final $foo"?What's the final value? The last "yield"ed value or a return?
"yield final" would mark the final result of the coroutine, as opposed to the intermediate values passed out with a normal "yield".
Just to give you some real world example:
If you're using "return", it would look like that:public function getSession ($sessionId) {
$result = yield $this->redis->get("session.{$sessionId}")); // We're waiting here until redis responded.
return json_decode($result);
}My suggestion is simply to change the keyword:
public function getSession ($sessionId) {
$result = yield $this->redis->get("session.{$sessionId}")); // We're waiting here until redis responded.
yield final json_decode($result);
}The reasoning being that when you run getSession(42), it doesn't return the result of
json_decode()
, it returns a pointer to the coroutine's state, which you can resume later.Actually, I don't think that example makes sense, because JSON gets sent out at the first yield, and then sent back in, so the caller would look something like this:
$foo = getSession(42);
$json_data = $foo->current();
$foo->send($json_data);
$decoded = $foo->getReturn();But never mind, I think we both get the idea.
I understand the desire for a "final result", but I don't like reusing the word "return", because it's never "returned" as the result of running the function, it's just made available through some specific method/syntax.
Regards,
Rowan Collins
[IMSoP]
Hey,
We currently already have "return;" (without value) as allowed syntax to terminate Generator execution. Thus, the logical consequence is just adding a value to that return.
Also… if you "yield final" a value… then it logically would go into the generator resolving function as it's just like a normal yield (it's a last one, but still a yield). Would be weird to yield a value as explicit return value.
When we want to return, it should be also a real *return". Yes, it is just accessible through special syntax (or a method), but it's still returning into the calling frame.
Why should the word "return" be unique to methods or functions?
Bob
Bob Weinand wrote on 03/03/2015 12:08:
Am 03.03.2015 um 12:34 schrieb Rowan Collins rowan.collins@gmail.com:
Niklas Keller wrote on 03/03/2015 10:55:
Gr, top-posting...
Sorry, was on mobile. ;-)
However, since the existence of the word "yield" is the only thing that marks a coroutine now, how about using a variant of that for the final value, e.g. "yield final $foo"?
What's the final value? The last "yield"ed value or a return?
"yield final" would mark the final result of the coroutine, as opposed to the intermediate values passed out with a normal "yield".Just to give you some real world example:
If you're using "return", it would look like that:public function getSession ($sessionId) {
$result = yield $this->redis->get("session.{$sessionId}")); // We're waiting here until redis responded.
return json_decode($result);
}
My suggestion is simply to change the keyword:public function getSession ($sessionId) {
$result = yield $this->redis->get("session.{$sessionId}")); // We're waiting here until redis responded.
yield final json_decode($result);
}The reasoning being that when you run getSession(42), it doesn't return the result of
json_decode()
, it returns a pointer to the coroutine's state, which you can resume later.Actually, I don't think that example makes sense, because JSON gets sent out at the first yield, and then sent back in, so the caller would look something like this:
$foo = getSession(42);
$json_data = $foo->current();
$foo->send($json_data);
$decoded = $foo->getReturn();But never mind, I think we both get the idea.
I understand the desire for a "final result", but I don't like reusing the word "return", because it's never "returned" as the result of running the function, it's just made available through some specific method/syntax.
Regards,
Rowan Collins
[IMSoP]
Hey,We currently already have "return;" (without value) as allowed syntax to terminate Generator execution. Thus, the logical consequence is just adding a value to that return.
Hm, I didn't realise that. I suppose it makes sense that you'd want a
way to do that, but it does make it very hard to see if something's
going to behave as a function or a coroutine if they use the same
keywords to mean different things.
When we want to return, it should be also a real *return". Yes, it is just accessible through special syntax (or a method), but it's still returning into the calling frame.
Well, so is "yield" - it just leaves the Generator in a state that can
be resumed, whereas "return"/"yield final" leaves it in a final,
non-resumable state.
Why should the word "return" be unique to methods or functions?
It just doesn't feel like the same thing as a return value to me, for
the same reason a generator doesn't feel like the same thing as a
function. In "function foo() { return 42; }", "return" means "this is
what you'll get when you run foo()"; in "function foo() { return 42;
yield; }", what you get when you run foo() is a pointer to the resumable
state, and return means "this is what you'll get if you ask the
generator/coroutine instance you get by running foo() for its final result".
Regards,
Rowan Collins
[IMSoP]
2015-03-03 13:27 GMT+01:00 Rowan Collins rowan.collins@gmail.com:
Bob Weinand wrote on 03/03/2015 12:08:
Am 03.03.2015 um 12:34 schrieb Rowan Collins rowan.collins@gmail.com:
Niklas Keller wrote on 03/03/2015 10:55:
Gr, top-posting...
Sorry, was on mobile. ;-)
However, since the existence of the word "yield" is the only thing that marks a coroutine now, how about using a variant of that for the
final
value, e.g. "yield final $foo"?What's the final value? The last "yield"ed value or a return?
"yield final" would mark the final result of the coroutine, as opposed
to the intermediate values passed out with a normal "yield".Just to give you some real world example:
If you're using "return", it would look like that:
public function getSession ($sessionId) {
$result = yield $this->redis->get("session.{$sessionId}")); //
We're waiting here until redis responded.
return json_decode($result);
}My suggestion is simply to change the keyword:
public function getSession ($sessionId) {
$result = yield $this->redis->get("session.{$sessionId}")); //
We're waiting here until redis responded.
yield final json_decode($result);
}The reasoning being that when you run getSession(42), it doesn't
return the result ofjson_decode()
, it returns a pointer to the coroutine's
state, which you can resume later.Actually, I don't think that example makes sense, because JSON gets sent
out at the first yield, and then sent back in, so the caller would look
something like this:$foo = getSession(42);
$json_data = $foo->current();
$foo->send($json_data);
$decoded = $foo->getReturn();But never mind, I think we both get the idea.
I understand the desire for a "final result", but I don't like reusing
the word "return", because it's never "returned" as the result of running
the function, it's just made available through some specific method/syntax.Regards,
Rowan Collins
[IMSoP]Hey,
We currently already have "return;" (without value) as allowed syntax to
terminate Generator execution. Thus, the logical consequence is just adding
a value to that return.Hm, I didn't realise that. I suppose it makes sense that you'd want a way
to do that, but it does make it very hard to see if something's going to
behave as a function or a coroutine if they use the same keywords to mean
different things.When we want to return, it should be also a real *return". Yes, it is
just accessible through special syntax (or a method), but it's still
returning into the calling frame.Well, so is "yield" - it just leaves the Generator in a state that can be
resumed, whereas "return"/"yield final" leaves it in a final, non-resumable
state.Why should the word "return" be unique to methods or functions?
It just doesn't feel like the same thing as a return value to me, for the
same reason a generator doesn't feel like the same thing as a function. In
"function foo() { return 42; }", "return" means "this is what you'll get
when you run foo()"; in "function foo() { return 42; yield; }", what you
get when you run foo() is a pointer to the resumable state, and return
means "this is what you'll get if you ask the generator/coroutine instance
you get by running foo() for its final result".Regards,
Rowan Collins
[IMSoP]
It's still different from "return". You said, it "mark[s] the final
result", so I'd expect that finally yielded value would be at the iterator,
but it's not. It's separate.
How would you call the method then, that makes that "yield final" value
available?
Regards, Niklas
Niklas Keller wrote on 03/03/2015 12:52:
2015-03-03 13:27 GMT+01:00 Rowan Collins <rowan.collins@gmail.com
mailto:rowan.collins@gmail.com>:Bob Weinand wrote on 03/03/2015 12:08: Why should the word "return" be unique to methods or functions? It just doesn't feel like the same thing as a return value to me, for the same reason a generator doesn't feel like the same thing as a function. In "function foo() { return 42; }", "return" means "this is what you'll get when you run foo()"; in "function foo() { return 42; yield; }", what you get when you run foo() is a pointer to the resumable state, and return means "this is what you'll get if you ask the generator/coroutine instance you get by running foo() for its final result".
It's still different from "return". You said, it "mark[s] the final
result", so I'd expect that finally yielded value would be at the
iterator, but it's not. It's separate.How would you call the method then, that makes that "yield final"
value available?
You mean instead of ->getReturn()? Something like ->getFinalValue() or
->getFinalResult()
Regards,
Rowan Collins
[IMSoP]
Daniel Lowrey wrote on 01/03/2015 23:52:
Hi folks,
I'd like to initiate discussion on a proposal to implement generator
delegation via the following new syntax inside generator functions:yield * <expr>
The Generator Delegation RFC is available here:
https://wiki.php.net/rfc/generator-delegation
This proposal is conceptually related to (and requires functionality
proposed by) the forerunning Generator Return Expressions RFC here:https://wiki.php.net/rfc/generator-return-expressions
Thanks for your time,
Daniel
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?
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;
}
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"?
Regards,
Rowan Collins
[IMSoP]