Not working on a project; opening the proposal here to branch off the discussion in here
In the ongoing discussion to add await, it was suggested that "completed" be added as an implied second parameter to yield(x), which would then be used instead of the new operator await.
This is a counter-proposal to add a Python-like functionality to yield instead, in addition to the new operator. As a bonus, it will allow easy usage of algebraic effects patterns.
When using yield() without parameters, we can use resume from the caller to send information into the callee. However, it's difficult to send information back to the caller in this back-and-forth manner.
This is the biggest reason why I haven't really ever used .resume() in practice. Usually, I'm more interested in getting information out of a function, not as much into it.
Nowadays, to pass information out from a concurrent coroutine, you need to pass some common container (like an array) into it by parameter, and use that as shared state. This is bug-prone and requires you to change the interface of the function when you decide that you need that channel of information.
The following code prints "ABCDEF", and illustrates a current way to do this.
func foo():
var in_channel = [null]
# foo --> bar, by parameter
var state = bar("A", in_channel)
# foo <-- bar, by shared state
var from_bar = in_channel[0]
print(from_bar) # "B"
# foo --> bar, by .resume
state = state.resume("C")
# foo <-- bar, by shared state
from_bar = in_channel[0]
print(from_bar) # "D"
# foo --> bar, by .resume
# foo <-- bar, by return
from_bar = state.resume("E")
print(from_bar) # "F"
func bar(x, in_channel):
print(x) # "A"
in_channel[0] = "B"
var from_foo = yield()
var print(from_foo) # "C"
in_channel[0] = "D"
from_foo = yield()
print(from_foo) # "E"
return "F"
As you can see, using in_channel is clunky and feels wrong in terms of shared state.
In Python, yield is used to send information out of the running generator function into the caller's context. This proposal aims to add a way to use yield more in line with what a Python programmer would expect.
My proposal is as follows: add a yielded_value property to GDScriptFunctionState that contains the value of x, if yield(x) was called with a single parameter (and null otherwise). This way, there is a two-way communication channel between caller and callee. This code does the same as the previous one, but hopefully in a more elegant manner.
func foo():
# foo --> bar, by parameter
var state = bar("A")
# foo <-- bar, by yield()
var from_bar = state.yielded_value
print(from_bar) # "B"
# foo --> bar, by .resume
state = state.resume("C")
# foo <-- bar, by yield()
from_bar = state.yielded_value
print(from_bar) # "D"
# foo --> bar, by .resume
# foo <-- bar, by return
from_bar = state.resume("E")
print(from_bar) # "F"
func bar(x):
print(x) # "A"
var from_foo = yield("B")
var print(from_foo) # "C"
from_foo = yield("D")
print(from_foo) # "E"
return "F"
This will not break compatibility with current use of yield, since you can't use it with a single parameter currently. I just think this is a much better idea than making "completed" implied, since then we don't make an already pretty confusing function call even more confusing.
As a further (compatibility-breaking) proposal, I suggest splitting the functionality of yield() so that the two-parameter version gets moved to await(). As such, we'd have a better-behaved function interface for both:
yield(value=null)
and
await(object, signal='completed')
No more counting the number of arguments in the parser.
I hope it's okay to break the question form in this case. If not, is there a proper place to make a proposal in this format? I don't really have a current project that would benefit from this, since I have avoided this pattern because of the lack of this functionality.
I didn't actually read the proposal, but just to leave a note: I intend to break some stuff in GDScript for Godot 4 and the yield functionality is one of those. I'll share my proposals when I get back from my trip.
Summing up, we'll probably remove yield (maybe save it for actual generators in the future) and just use await with signals. It's likely that the GDScriptFunctionState object won't be accessible anymore. Since signals will be first-class in Godot for, we can do simply: await $Button.pressed. Function calls will also be allowed: await my_func().
This proposal is for the argumentless functionality of yield(), which has a very different use-case from the 2-argument version. They could coexist with await without much overlap, I think.
I don't think we really need the argumentless yield as it is today. A lot of languages live by without any kind of continuation object and I don't think it's super useful in GDScript, which is meant to be simple.
Generators, OTOH, can be very useful. I'm not really planning to add them soon, but I do want to investigate how they could work in GDScript.
What about yielding for a frame? I use that one a ton.
I also use yield timeout a lot. I use these so much so I'd love simple wait_s(4) wait_f(20) functions or something like that, if yield will no longer be there.
Await will do the same as yield in these cases.
On Fri, 14 Feb 2020 at 19:48, Seel notifications@github.com wrote:
What about yielding for a frame? I use that one a ton.
I also use yield timeout a lot. I use these so much so I'd love simple
wait_s(4) wait_f(20) functions or something like that, if yield will no
longer be there.—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
https://github.com/godotengine/godot-proposals/issues/453?email_source=notifications&email_token=ABQ3BPIS2XSQAZKHJQTDFCLRC4NSDA5CNFSM4KRXPXU2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEL2WTDQ#issuecomment-586508686,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/ABQ3BPIQ5MLI5PHJMO5PL7DRC4NSDANCNFSM4KRXPXUQ
.
Most helpful comment
I didn't actually read the proposal, but just to leave a note: I intend to break some stuff in GDScript for Godot 4 and the
yieldfunctionality is one of those. I'll share my proposals when I get back from my trip.Summing up, we'll probably remove
yield(maybe save it for actual generators in the future) and just useawaitwith signals. It's likely that the GDScriptFunctionState object won't be accessible anymore. Since signals will be first-class in Godot for, we can do simply:await $Button.pressed. Function calls will also be allowed:await my_func().