-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 David Herman's comment about JS1.7 generators spurred me to consider if we could achieve the goals stated for single-frame continuations as a broadly useful mechanism for replacing CPS code with more natural flow for constructs like promises and one-time event handlers *and* preserve the syntax of and compatibility with JS1.7 generators. At the risk of creating confusion for a somewhat already complicated concept, I wanted to put forth alternate proposal for single-frame continuations. The tenants here are:
* Use of the "yield" keyword as the syntax addition for capturing continuations * Can default to behaving like JS1.7 generators * Still maintain the principles of avoiding multi-frame stack splitting with interleaving hazards and VM implementation burden * Alternate continuation handlers can be used that control the continuation of a function can be employed for other purposes. Unlike JS1.7 generators, code would could have the ability to: ** Begin execution of the body of the function when it is called ** Recreate call stacks ** Yielding functions can exit through a normal return (using the return operator to return a value). The key idea of this approach is that when a function is called that contains a yield operator, rather than following a hard-coded prescription to return an generator/iterator object, this triggers a call to the startCoroutine variable (from the current lexical scope) that can implement various different behaviors, including immediately executing the function and handling various values differently, or returning an iterator. The startCoroutine function would be called with a controller object that provides a "resume" function and a "suspended" boolean property, that can be used by the startCoroutine to resume execution, get the result of the function exiting through yield and return operators and determine when the function is complete (if it exited with a return). In addition the engine could define a global startCoroutine variable such that returning iterators would be the default behavior of a yielding functions, but this could easily be overriden (globally or in the local scope). This ultimately could be used a similar fashion as the other proposal, but with slightly different syntax (use of "yield" instead "->()") like: startCoroutine = ... some promise style library's handler ... showData = function(){ var data = yield xhrGet({url:"my-data"}); // async if(data.additionalInformation){ // there is additional data we need to get, load it too data.additionalData = yield xhrGet({url:data.additionalInformation}); // async } someElement.innerHTML = template.process(data); }; Here is the definition, adapted from some of David Herman's corrections of my previous algorithm (hopefully I did a little better this time): semantics of a function that contains the yield keyword: 1. Define a *controller* object with a "suspended" property set to true. 2. Set the "resume" property to be a function defined as *resume*. 3. Let *k* be the current function continuation (including code, stack frame, and exception handlers). 4. Let *startCoroutine* be the result of looking up "startCoroutine" variable starting in the current scope of the function 5. Call *startCoroutine* passing the *controller* object 6. Let *returned* be the value returned from the call to startCoroutine. 7. Return *returned* from this function semantics of calling *resume* with argument *v*: 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 created *k*). 3. Call *v* with no arguments at the completion of the point of capture. 4. Continue executing from the point of capture using the result of the call to *v*. 5. If a yield operator is encountered proceed to the semantics of "yield" <expr>. 6. If a return operator is encountered, set the *controller* object's "suspended" property set to false. 7. Return the value of evaluated expression provided to the return operator from the initiating call to *resume*. semantics of "yield" <expr>: 1. Evaluate the expression and store the result as *result*. 2. Let *k* be the current function continuation (including code, stack frame, and exception handlers). 3. Exit the current activation. 4. Return *result* from the initiating call to *resume* An example of an equivalent translation, first a function that uses a yield operator: function foo(){ return (yield bar()) + 2; } Would effectively act like the following ES5 code: function foo(){ var $pointer = 0; // used to keep track of where we are // create the controller var $controller = { suspended: true, resume: function(getValue){ var nextValue; if(getValue){ nextValue = getValue(); } if($pointer == 0){ // start of function $pointer++; // execute until the yield operator and return the value passed to yield return bar(); } if($pointer == 1){ // continuation from the yield $pointer++; // continue execution until the return operator $controller.suspended = false; // no more yield, function is done return nextValue + 2; } if($pointer > 1){ throw new Error("Can not resume a function that has returned"); } } }; // call the startCoroutine function return startCoroutine($controller); } And we can define the global variable startCoroutine such that functions with a yield operator act like JS1.7 generators by default: // Default implementation of startCoroutine, returns JS1.7 generators startCoroutine = function(controller){ // return a JS1.7 iterator return { send: function(value){ var nextValue = controller.resume(function(){ return value; }); if(!controller.suspended){ throw StopIteration; } return nextValue; }, throws: function(error){ controller.resume(function(){ throw error; }); }, next: function(){ var nextValue = controller.resume(); if(!controller.suspended){ throw StopIteration; } return nextValue; } }; }; A JavaScript module that wanted the behavior of the previous proposal could easily define startCoroutine in local (or global) variable and implement it oneself. One potential drawback to this proposal is that it creates some contention for the startCoroutine variable (at least at the global level). Originally, I was thinking of having startCoroutine be a property of the calling function (default behavior defined at Function.prototype.startCoroutine), but having the behavior defined through lexical scope seems like the most appropriate level of specificity and ease of use. Of course the advantage of this proposal is that it is compatible with JS1.7 generators, and utilizes an existing ES5 strict-mode future reserved keyword (presumably for what it is actually intended for). Anyway, I am curious if this would be a more desirable approach for an evolutionary addition to EcmaScript. Thanks, Kris -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (MingW32) Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/ iEYEARECAAYFAku0qogACgkQ9VpNnHc4zAwczgCgpZaF0xMT8Sy5EUY9Pdc/Xqd9 CtYAoIOUNcwjybbr6Pve9+ZmIsbQe5LH =I7y7 -----END PGP SIGNATURE----- _______________________________________________ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss