One of the semantics I suggested and then dismissed for single-frame 
continuations was based directly on the operators "shift" and "reset" from the 
PL research literature.[1] To my eye, when we dressed them up to look like a 
function call (with "->"), they suggested that we were calling a function in 
the current exception handlers when they weren't.

But if we stop being creative with syntax and just use a more traditional 
prefix operator:

    UnaryExpression ::= ... | "shift" UnaryExpression

then let's revisit the semantics. Evaluating one of these expressions in the 
stack S(A) -- ie, a base stack S with current function activation A -- would do 
the following:

    1. Evaluate the argument expression to a value v.
    2. Suspend and capture A as a continuation value k.
    3. Remove A from the stack, leaving S as the stack.
    3. Call v with k as its single argument in the stack S.

We have the same representation choices for k; it could simply be a function, 
or it could be an object with a few methods, most likely "send", "throw", and 
"close". (I still think we couldn't accommodate anything more powerful than 
one-shot continuations, mostly because of "finally".) Re-entering the 
activation simply pushes it on top of the stack, including its suspended 
exception handlers (just the ones installed in the function body). It also 
closes over its scope chain, of course.

My original quibble with this semantics and the old notation had been that when 
you said:

    try { f->(x,y,z) } catch (e) { ... }

it looked like any exceptions thrown by f would be caught by the try-block, but 
that wouldn't be the case. But I think this was more a syntactic issue. Just 
like other powerful control operators like "fork" and "call/cc", a continuation 
operator overrides the ordinary flow of control. As long as the notation 
doesn't hide this fact, it's something programmers would have to be aware of-- 
just like they have to with "yield", "return", "break", and "continue".

Example:

    function f() {
        try {
            for (let i = 0; i < K; i++) {
                farble(i);
                // suspend and return the activation
                let received = shift (function(k) { return k });
                print(received); // this will be 42
            }
        }
        catch (e) { ... }
    }

    let k = f(); // starts running and return the suspended activation
    ...
    k.send(42); // resume suspended activation

It wouldn't be hard to show a proof-of-concept implementation of JS 1.7 
generators with this construct, as well as a Lua-style coroutine API (one frame 
only, though, of course).

Notice that, unlike the CPS people normally have to write in, "shift" 
essentially flips around the control flow so that the callback is what's 
evaluated immediately, whereas the remainder of the function is delayed for 
later. Because shift expects a function argument, programmers/library writers 
could come up with conveniences for common idioms.

Also notice that, unlike JS 1.7 "yield", a function that uses "shift" is not 
special in that it doesn't immediately suspend its body when you first call it. 
But because it's a syntactic operator, it's more manageable for implementors of 
high-performance ES engines, since they can trivially detect whether a function 
may need to suspend its activation.

One last thought: a variation you sometimes see is something like:

    Expression ::= ... | "shift" "(" Identifier ")" Expression

(with the precedence worked out-- yadda yadda), which avoids the function 
indirection and simply binds the continuation to the identifier and evaluates 
the argument expression. This would be slightly more wonky in JS, because of 
the lack of "TCP" -- it's unclear what the "arguments" array should be bound 
to, and if we had something like let expressions with statement bodies, 
"return" would be weird.

It's also likely that retaining the function indirection makes it more 
convenient to use handlers, e.g.:

    function sleep(ms) {
        return function(k) {
            window.setTimeout(function() { k.send() }, ms)
        }
    }

    function iamtired() {
        ...
        shift sleep(100);
        ...
    }

Dave

[1] http://en.wikipedia.org/wiki/Delimited_continuation

_______________________________________________
es-discuss mailing list
[email protected]
https://mail.mozilla.org/listinfo/es-discuss

Reply via email to