tc39 / proposal-async-explicit-resource-management

ECMAScript Async Explicit Resource Management
https://tc39.es/proposal-async-explicit-resource-management/
BSD 3-Clause "New" or "Revised" License
55 stars 3 forks source link

Specify `using await` syntax #6

Closed rbuckton closed 1 year ago

rbuckton commented 1 year ago

This updates the proposal specification text to reflect the tentative outcome of #1:

Fixes #1

github-actions[bot] commented 1 year ago

A preview of this PR can be found at https://tc39.es/proposal-async-explicit-resource-management/pr/6.

ljharb commented 1 year ago

Can you using await x; with no assignment?

rbuckton commented 1 year ago

This PR also cleans up a few sections of the non-async version of the proposal that I plan to backport:

rbuckton commented 1 year ago

Can you using await x; with no assignment?

No.

ljharb commented 1 year ago

Can you using x; with no assignment? If the two aren't in sync, why? (if they are, then, great)

rbuckton commented 1 year ago

Can you using x; with no assignment? If the two aren't in sync, why? (if they are, then, great)

No, you can't do using x; either, just like you can't do const x;.

bakkot commented 1 year ago

I like everything about this except that it has using await instead of async using. I think that await suggests the RHS would be awaited, whereas async suggests that there is awaiting later, and so async is more appropriate. I do not expect this to be difficult for readers to understand. And @mhofman said in that thread that either syntax would meet the requirement that "a simple syntactic glance at the source allows to realize an interleaving point does exist", so it sounds like that would be broadly acceptable given the other choices made about the semantics in this PR.

I see someone from the community has expressed the same intuition as me.

(Seems fine to merge this PR as-is and continue discussing the spelling in another issue, though, since this PR establishes more than just the spelling.)

rbuckton commented 1 year ago

I like everything about this except that it has using await instead of async using. I think that await suggests the RHS would be awaited, whereas async suggests that there is awaiting later, and so async is more appropriate.

I chose using await as @erights has indicated his position that await and yield are the only appropriate keywords to use to indicate implicit interleaving points, and that async is not a sufficient indicator. While that preference may have been weakened somewhat per the comment you referenced, I still think using await is the better choice. The async keyword today does not indicate "awaiting later", but instead indicates that the body of the function may contain await, and that executing the function will return a Promise. Instead, the await in for-await-of is a better indication of "awaiting later" (though it also "awaits now").

I also chose using await as it more closely resembles the equivalent syntax in C# (namely await using x = ...) as the syntactic similarity is helpful for multi-language developers. However, I chose the using await ordering because await using would be an ambiguous parse given that using is a valid IdentifierReference.

Finally, using await works more cleanly with the static semantics for ClassStaticBlockDeclaration, which has an early error for "Contains await".

bakkot commented 1 year ago

as @erights has indicated his position that await and yield are the only appropriate keywords to use to indicate implicit interleaving points

I disagree with that position. I don't think developers will trip over async using - it's easy to learn. And in this case we're indicating an interleaving point elsewhere, so it's already a somewhat different thing.

The async keyword today does not indicate "awaiting later", but instead indicates that the body of the function may contain await, and that executing the function will return a Promise.

Right - a meaning clearly inapplicable to using. So I think it will be easy for readers to generalize to "this is the asynchronous version of this thing, where the precise meaning of 'asynchronous version' depends on the thing".

By contrast, await today means "executing this statement will perform an await", which is not true here. And since that's a thing which could easily apply to using declarations (i.e., by awaiting the RHS), there's no indication to readers that this interpretation is inapplicable here. I would really prefer to avoid choosing a syntax which has an obvious interpretation which is incorrect.

I also chose using await as it more closely resembles the equivalent syntax in C#

async using is about as close to await using as using await is, surely?

Finally, using await works more cleanly with the static semantics for ClassStaticBlockDeclaration, which has an early error for "Contains await".

As I've mentioned elsewhere, I think that the difficulty of specifying one semantics or another should basically never factor into our user-facing decisions about the language, except for things which will be encountered very rarely and which we are approximately ambivalent between - not the case here. Whatever we settle on, as long as it's implementable, as editor I am happy to find a way to write that down.

erights commented 1 year ago

Is there a way to provoke the github actions to keep https://tc39.es/proposal-async-explicit-resource-management/pr/6/ up to date?

rbuckton commented 1 year ago

It already should be doing this.

erights commented 1 year ago

Thanks! (Looking again, I'm not sure why I was confused)

mhofman commented 1 year ago

Given that the actual interleaving point is no longer explicit, we do have a preference for the using await syntax as it carries the notion there is an interleaving point better than async using does. As @rbuckton mentions, async is used to denote await is syntactically available, which is weaker than "an interleaving will occur". This is however not a blocker for us as linting tools should work equally with either.

littledan commented 1 year ago

Not having given this PR a totally thorough review, it looks reasonable to me, and delivers on the promises made by parts of the README today (which is, without this, a little incoherent, as I noted in #7). I recommend that you land this patch now, as review continues, to avoid the confusion for future reviewers that I went through just now :)

(A tangent, and I'm a bit late to this, but it feels kinda crazy to me that Await isn't "viral" to callers in spec text the way it is in JS code, especially since throwing an exception is viral. I wonder if we should change that. But not today.)

bakkot commented 1 year ago

On using await vs of async using, as a datapoint, python uses async with as the async version of its with statement, which serves a similar role. That inclines me more towards async using as the async version of using.

rbuckton commented 1 year ago

On using await vs of async using, as a datapoint, python uses async with as the async version of its with statement, which serves a similar role. That inclines me more towards async using as the async version of using.

Both C# and Python heavily influenced the design of this proposal. C#'s variant uses await, and Python's variant uses async, so leveraging prior art in this case is a bit of a wash. Instead, I'd rather leverage the usage of async and await in JS as the deciding factor.

Every instance of await in JS today indicates that we should "pause execution and wait for the result". In the case of AwaitExpression, this is an immediate consequence. In the case of for-await-of, we perform multiple "awaits" during execution:

As a result, for-await-of has both an immediate consequence of its await (iter.next()), as well as a delayed consequence (iter.return()).

Every instance of async in JS today indicates "something that must eventually be "awaited" to observe its result" (in this sense, I am grouping both await and Promise.prototype.then as "awaiting" the result). Even proposals like async do fully align with this meaning.

The async version of the using syntax more closely aligns with the delayed consequence behavior of for-await-of, thus I think it is important that the await keyword be involved in the syntax. When I brought async using up for discussion, it was intentionally paired with a using await {} block that still used the await keyword as an indicator. The async keyword in that case was purely to differentiate that version of using from the sync version of using.

using await as a standalone declaration more closely aligns with the existing meaning of await in JS, and ensures we're still using await to call out that a "pause execution and wait for the result" operation is still in effect, even if the consequence itself is delayed. As such, I still plan to go to plenary with the using await syntax.

rbuckton commented 1 year ago

If necessary, we can bikeshed the use of await or async in a separate issue thread, or during plenary.