New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better closures, or promise-handling, in the spec #933

Open
domenic opened this Issue Jun 13, 2017 · 6 comments

Comments

Projects
None yet
4 participants
@domenic
Member

domenic commented Jun 13, 2017

Over in the async iteration spec, we're having to write up more and more annoying "closures" as sections. Examples, from the current spec and from async iteration:

The vast majority of these are basically trying to respond to promises, running some spec steps in reaction to their fulfillment or rejection. (Those that are not: promise-rejection-functions, promise-resolve-functions, and proxy-revocation-functions.)

I would love to explore one or both of the following paths:

  1. A way of writing these closures inline, in the algorithms that need to create them, as you would do in ECMAScript.
    • Probably we would want to preserve the current spec's explicit listing of "closed over" values, unlike ECMAScript code.
  2. Algorithmic conventions for operating on promises that allow us to generally react to promises. Web specs are already using some; of those I think the most important are, in order from most- to less-important:
    • Upon fulfillment / upon rejection
    • Transforming
    • A promise resolved with x / a promise rejected with x
Example of (1)

CreateResolvingFunctions(promise)

When CreateResolvingFunctions is performed with argument promise, the following steps are taken:

  1. Let alreadyResolved be a false.
  2. Let resolve be the built-in function object defined by the following steps, [closing over] promise and alreadyResolved, and taking the argument resolution:
    1. If alreadyResolved is true, return undefined.
    2. Set alreadyResolved to true.
    3. If SameValue(resolution, promise) is true, then...
    4. ...
  3. Let reject be the built-in function object defined by the following steps, [closing over] promise and alreadyResolved, and taking the argument reason:
    1. If alreadyResolved is true, return undefined.
    2. Set alreadyResolved to true.
    3. Return RejectPromise(promise, reason).
  4. Return a new Record { [[Resolve]]: resolve, [[Reject]]: reject }.
Example of (2)

AsyncFunctionAwait ( value )

  1. Let asyncContext be the running execution context.
  2. Let valueAsPromise be [a promise resolved with] value.
  3. Upon fulfillment of valueAsPromise with fulfillmentValue, perform the following steps, [closing over] asyncContext:
    1. Let prevContext be the running execution context.
    2. Suspend prevContext.
    3. Push asyncContext onto the execution context stack; asyncContext is now the running execution context.
    4. Resume the suspended evaluation of asyncContext using NormalCompletion(fulfillmentValue) as the result of the operation that suspended it. Let result be the value returned by the resumed computation.
    5. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context.
    6. Return Completion(result).
  4. Upon rejection of valueAsPromise with rejectionReason, perform the following steps, [closing over] asyncContext:
    1. Let prevContext be the running execution context.
    2. Suspend prevContext.
    3. Push asyncContext onto the execution context stack; asyncContext is now the running execution context.
    4. Resume the suspended evaluation of asyncContext using Completion{[[Type]]: throw, [[Value]]: rejectionReason, [[Target]]: empty} as the result of the operation that suspended it. Let result be the value returned by the resumed computation.
    5. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context.
    6. Return Completion(result).
  5. Remove asyncContext from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context.
  6. Set the code evaluation state of asyncContext such that when evaluation is resumed with a Completion resumptionValue the following steps will be performed:
    1. Return resumptionValue.
  7. Return.

I am willing to spend significant time refactoring the current spec if I can get the go-ahead, for either (1) or (2) or both.

@bterlson

This comment has been minimized.

Show comment
Hide comment
@bterlson

bterlson Jun 13, 2017

Member

I like upon-fulfillment and upon-rejection a lot (with "closures"). How frequently are the others coming up in Async Iteration work?

What is the concern with omitting the "closing over" clause? The default assumption of "indented steps see outer named aliases" seems fine.

Member

bterlson commented Jun 13, 2017

I like upon-fulfillment and upon-rejection a lot (with "closures"). How frequently are the others coming up in Async Iteration work?

What is the concern with omitting the "closing over" clause? The default assumption of "indented steps see outer named aliases" seems fine.

@domenic

This comment has been minimized.

Show comment
Hide comment
@domenic

domenic Jun 13, 2017

Member

How frequently are the others coming up in Async Iteration work?

"a promise resolved with" is very frequent. The others less so... I was thinking "transforming" would be used by Promise.prototype.finally or Promise.all, but I see those call the public API methods, so that doesn't quite work. And I guess it shouldn't; we should use proper closure machinery, possibly made simpler by (1), for closures that are actually exposed to author code.

What is the concern with omitting the "closing over" clause? The default assumption of "indented steps see outer named aliases" seems fine.

I can't be sure, but I vaguely imagine it's to make you aware of the "cost" of the closure, and to make it easier to translate into languages (like C++) which don't have auto-capturing closures? I'd be OK omitting it.

Member

domenic commented Jun 13, 2017

How frequently are the others coming up in Async Iteration work?

"a promise resolved with" is very frequent. The others less so... I was thinking "transforming" would be used by Promise.prototype.finally or Promise.all, but I see those call the public API methods, so that doesn't quite work. And I guess it shouldn't; we should use proper closure machinery, possibly made simpler by (1), for closures that are actually exposed to author code.

What is the concern with omitting the "closing over" clause? The default assumption of "indented steps see outer named aliases" seems fine.

I can't be sure, but I vaguely imagine it's to make you aware of the "cost" of the closure, and to make it easier to translate into languages (like C++) which don't have auto-capturing closures? I'd be OK omitting it.

@jmdyck

This comment has been minimized.

Show comment
Hide comment
@jmdyck

jmdyck Jun 14, 2017

Collaborator

The spec uses the term "internal closure" for (roughly) an anonymous abstract op with access to 'outer' metavariables. Path (1) is proposing (roughly) an anonymous built-in function object whose call-semantics are given by steps with access to 'outer' metavariables. The spec should make it clear (via naming and description) that the two things are similar but distinct.

Collaborator

jmdyck commented Jun 14, 2017

The spec uses the term "internal closure" for (roughly) an anonymous abstract op with access to 'outer' metavariables. Path (1) is proposing (roughly) an anonymous built-in function object whose call-semantics are given by steps with access to 'outer' metavariables. The spec should make it clear (via naming and description) that the two things are similar but distinct.

@domenic

This comment has been minimized.

Show comment
Hide comment
@domenic

domenic Jun 14, 2017

Member

Goodness, I had no idea that the spec had a notion of "internal closure" already. I see it is only used inside the notation of RegExp pattern semantics; strange.

Member

domenic commented Jun 14, 2017

Goodness, I had no idea that the spec had a notion of "internal closure" already. I see it is only used inside the notation of RegExp pattern semantics; strange.

@jmdyck

This comment has been minimized.

Show comment
Hide comment
@jmdyck

jmdyck Jun 14, 2017

Collaborator

Yeah, there's a lot of strange stuff in RegExp semantics!

Collaborator

jmdyck commented Jun 14, 2017

Yeah, there's a lot of strange stuff in RegExp semantics!

@littledan

This comment has been minimized.

Show comment
Hide comment
@littledan

littledan Jun 15, 2017

Member

Yeah, I was a bit confused at first by how the data passing in the RegExp spec works, though I imagine it's done that way because things would be very wordy if it were all explicit.

Anyway, the RegExp usage of closures is pretty different from the Promise usage; it seems like they should be addressed separately.

Member

littledan commented Jun 15, 2017

Yeah, I was a bit confused at first by how the data passing in the RegExp spec works, though I imagine it's done that way because things would be very wordy if it were all explicit.

Anyway, the RegExp usage of closures is pretty different from the Promise usage; it seems like they should be addressed separately.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment