Single frame continuations proposal
dherman at mozilla.com
Tue Mar 30 15:20:58 PDT 2010
Thanks for this proposal. I think it's got a lot going for it. I like the simplicity of the API, although I think it could be simplified even further.
> This is often called continuation passing style (CPS) and is
> complicated to use in non-linear control flow.
Yes, I think there's a pretty clear need for better language support for programming with event-loop-concurrent API's.
> I propose a new call
> operator that can expressed as a function local translation that can
> greatly ease the construction of code that involves resuming flow at a
> future point in time.
I don't think it *should* be expressed as a translation in the spec, but let's keep specification approaches separate from the discussion at hand.
> Traditional continuations were proposed for ES4, and rejected for a
> couple of very important reasons.
Uncontroversial; no one's championing full continuations for ES.
> generators have been successfully implemented in SM and Rhino without
> any unnecessary burdens on the VM since their semantics are local to a
> single function and do not introduce interleaving hazarads. But,
> generators are just too specialized and the API is too difficult to use
> for other CPS style mechanisms.
Your approach seems nicely Harmony-ous: small, simple, orthogonal. It would definitely be a cost, however, if there turned out to be some incompatibility with JS 1.7 generators. I don't think there is one, but it would be good to know-- and conversely, it'd be awesome if generators (minus the syntax) could really be implemented as a library, e.g. if "yield <expr>" could be simulated by calling:
I can't quite make sense of your pseudo-code, and I'm pretty sure it's not what you intended. I couldn't quite follow it enough to figure out what you meant.
> 1. evaluate the LHS expression and then the argument expressions
> (just like function calls)
> 2. Let *continuation* be defined as the capture of the current
> function activation's continuation
> 3. call the LHS value with the arguments (like a normal function
> calls), and let *result* be the value returned from the call
> 4. If the call throws an exception, throw the exception in the
> current stack.
> 5. If *result* is an object and it has a function value in the
> property named "continueWith", than proceed to step 7.
> 6. Resume *continuation*, using the value of *result* for the
> continued evaluation of the current expression (if the continuation is
> inside an expression).
Is this supposed to fall through?
> 7. Set an internal flag *waiting* to true for this call.
> 8. Call the function that is stored in the "continueWith" property
> of the *result*. The function should be called with one argument, a
> function value defined as *resume*. Let *continueWithResult* be the
> value returned from the call.
> 9. Exit the function returning *continueWithResult*
> 10. If and when *resume* is called, store the first argument as
> *resumeNextFrame* and proceed to step 10.
This is an infinite loop...
> 11. If internal flag *waiting* is false, throw an Error "The stack
> can not be resumed again".
> 12. Set the internal flag *waiting* to false.
> 13. Call *resumeNextFrame* and let *result* be set to the value
> returned from the call. Proceed to step 5.
I think it's more complicated than necessary. NarrativeJS seems expressive enough that I bet you could express your semantics as a library on top of that. As you say, we're not in the business of blessing libraries! :)
I would think the following sufficient and, IIRC, the same as NarrativeJS (roughly?):
semantics of <expr> "->" <arg-list>:
1. Evaluate the LHS expression and then the argument expressions
2. Let *k* be the current function continuation (including code, stack frame, and exception handlers).
3. Exit the current activation.
4. Call the LHS value with the argument values followed by *k*.
5. Return the result of the call.
semantics of calling *k* with argument *v* (defaults to the undefined value):
1. Create a new function activation using the stack chain from the point of capture.
2. Reinstall the function activation's exception handlers from the point of capture (on top of the exception handlers of the rest of the stack that has called *k*).
2. Use *v* as the completion of the point of capture.
3. Continue executing from the point of capture.
This is quite a bit simpler. And I suspect it should be sufficient to implement your above semantics as well as JS 1.7 generators. I will see if I can sketch a translation.
> * Don't mess with the current concurrency model - EcmaScript's current
> shared-nothing event-loop concurrency model is ideal for preventing
> concurrency hazards.
Agreed (other than the word "ideal").
> * Don't introduce traditional continuations
> * Consequently, I don't want to suggest any new runtime semantics
You have. :) </pedant>
But I would support a claim that you've held the line and suggested a very modest change to the runtime semantics.
> What I would like to see:
> * Easy construction of code flow that must span event turns through
> explicit single-frame continuations.
> * Composable API.
> * Preservation of causality
It'd be helpful to understand is why you felt these should be restricted to one-shot continuations (i.e., callable at most once).
More information about the es-discuss