As a certified PL amateur, this is very impressive.
Most async implementations end up bifurcating code into async/non-async flavors (much like const in C++), or require a specialized VM with different callstack semantics. I’m a big fan of async/await overall, but it still sometimes felt like something the compiler should help me more with. What’s more, I can’t imagine using async/await without a dynamic type system: you have to keep a clear mind on whether you’re dealing with a value, or the thunk returning a value. As the paper mentions, most async/await implementations are kind of one-off in the sense they’re hardwired into the language, rather than being building blocks.
Also, I love the phrase “islands of abstraction” to concisely describe the phenomenon that occurs when you need duplicate versions of functions for different contexts, be they monadic, async, const, etc.
I totally agree that this is a nicer syntax, and the right one for C++. However, it’s worth noting that it is not without tradeoffs.
Most async implementations end up bifurcating code into async/non-async flavors
That’s still happening here, but luckily, mostly behind the scenes: You need to compile resumable and not-resuable functions separately. The templating and constexpr machinery of C++ is being used to accomplish that. See the notes in this proposal about separate compilation. You either need to do one of these things:
Forfeit “up-and-out” resumability; switching to the “down” model by installing a scheduler (ie the spawn function)
Forfeit separate compilation by exposing the definition of your function in a header file.
Forfeit implicit call-site resumability; exporting a function that returns a resumable and executing it at the call site.
C++ can get away with this because it already has a source compatibility model instead of a binary compatibility one. Template libraries are explicitly not separately compiled. On the other hand, C#, or indeed C++/COM on Windows, where async/await originated, needs binary compatibility and full separate compilation.
You could encode resumability in to the existing module ABI, either with a distinguished type (like Promise) or with metadata attributes, and then make it implicit statically. However, if you do that, then older compilers wouldn’t be aware when calling things that return Promise or Task or whatever. Upgrading your compiler would create a situation where source code is broken absent call-site level hints to opt-in or opt-out of the type-aware implicit behavior. “await” is basically that hint, opt-in. This isn’t a problem in C++ because you need the newer compiler to call resumable functions in the first place!
To me, this whole situation sucks. We just need to bite the bullet and have lightweight threads in our operating system and/or language runtimes. There’s lots of challenges there, but it seems to be the only path forward that doesn’t simply layer more complexity on top of our existing type systems.
What’s more, I can’t imagine using async/await without a dynamic type system
I assume you meant a static type system? In that case, yeah, I agree: The bifurcation makes it truly annoying to keep T vs Promise straight. However, dynamic types is also a reason to justify the explicit async/await syntax. JavaScript couldn’t accomplish the same thing as C++ because it lacks static information necessary to perform the resumable rewrite during compilation.
One other thing worth mentioning is there is some prior art worth studying here:
This shows a publish date 2015 - does anyone know if the standards committee has seen it/discussed it/ripped it to shreds? Coroutines in C++ would make me pretty happy, but it has to go through committee first which is a huge step for any new feature.
Good catch. This appears to be a quasi-official negative reponse, from Gor Nishanov, co-author of the coroutines proposal, which seems to be the one on track to being standardized, and which the “resumable expressions” proposal was written in part as an objection or alternative to. The response acknowledges some points but believes they can be addressed in the coroutines proposal, which seems to have gone through several revisions, and now has a “working draft” dated 2017-03-03.
So my read is that the resumable-expressions proposal linked in this post is interesting for historical and/or intellectual reasons, but will probably not end up in the next C++ standard, where the coroutine draft is the one to watch.
As a certified PL amateur, this is very impressive.
Most async implementations end up bifurcating code into async/non-async flavors (much like
constin C++), or require a specialized VM with different callstack semantics. I’m a big fan of async/await overall, but it still sometimes felt like something the compiler should help me more with. What’s more, I can’t imagine using async/await without a dynamic type system: you have to keep a clear mind on whether you’re dealing with a value, or the thunk returning a value. As the paper mentions, most async/await implementations are kind of one-off in the sense they’re hardwired into the language, rather than being building blocks.Also, I love the phrase “islands of abstraction” to concisely describe the phenomenon that occurs when you need duplicate versions of functions for different contexts, be they monadic, async, const, etc.
I totally agree that this is a nicer syntax, and the right one for C++. However, it’s worth noting that it is not without tradeoffs.
That’s still happening here, but luckily, mostly behind the scenes: You need to compile resumable and not-resuable functions separately. The templating and constexpr machinery of C++ is being used to accomplish that. See the notes in this proposal about separate compilation. You either need to do one of these things:
C++ can get away with this because it already has a source compatibility model instead of a binary compatibility one. Template libraries are explicitly not separately compiled. On the other hand, C#, or indeed C++/COM on Windows, where async/await originated, needs binary compatibility and full separate compilation.
You could encode resumability in to the existing module ABI, either with a distinguished type (like Promise) or with metadata attributes, and then make it implicit statically. However, if you do that, then older compilers wouldn’t be aware when calling things that return Promise or Task or whatever. Upgrading your compiler would create a situation where source code is broken absent call-site level hints to opt-in or opt-out of the type-aware implicit behavior. “await” is basically that hint, opt-in. This isn’t a problem in C++ because you need the newer compiler to call resumable functions in the first place!
To me, this whole situation sucks. We just need to bite the bullet and have lightweight threads in our operating system and/or language runtimes. There’s lots of challenges there, but it seems to be the only path forward that doesn’t simply layer more complexity on top of our existing type systems.
I assume you meant a static type system? In that case, yeah, I agree: The bifurcation makes it truly annoying to keep T vs Promise straight. However, dynamic types is also a reason to justify the explicit async/await syntax. JavaScript couldn’t accomplish the same thing as C++ because it lacks static information necessary to perform the resumable rewrite during compilation.
One other thing worth mentioning is there is some prior art worth studying here:
“Implementing First-Class Polymorphic Delimited Continuations by a Type-Directed Selective CPS-Transform” - Rompf, et al
https://pdfs.semanticscholar.org/5649/bbc4cc8392918e19fbdb846872130d6fec67.pdf
This looks so much better than await/yield in JavaScript, Python and C#, because the await keyword is not viral.
This shows a publish date 2015 - does anyone know if the standards committee has seen it/discussed it/ripped it to shreds? Coroutines in C++ would make me pretty happy, but it has to go through committee first which is a huge step for any new feature.
Good catch. This appears to be a quasi-official negative reponse, from Gor Nishanov, co-author of the coroutines proposal, which seems to be the one on track to being standardized, and which the “resumable expressions” proposal was written in part as an objection or alternative to. The response acknowledges some points but believes they can be addressed in the coroutines proposal, which seems to have gone through several revisions, and now has a “working draft” dated 2017-03-03.
So my read is that the resumable-expressions proposal linked in this post is interesting for historical and/or intellectual reasons, but will probably not end up in the next C++ standard, where the coroutine draft is the one to watch.