Continuation.js is a project mainly written in ..., it's free.
Continuations for Javascript
NOTE! There is nothing yet, just this README file.
A continuations reifies (i.e. makes explicitly formulated and addressable) a point in the execution of software. Almost all languages have means to manipulate the order of executing instructions, namely control structures like if statements, loops, return statements, etc. With setjump in C one can "return" out of several levels of nested function calls, but not jump freely from function to function: if the function in which setjmp() was called has returned, a jump is not possible. Continuations can jump back and therefore are strictly more powerful than setjump points.
Some languages offer first-class continuations, for example Scheme with
call-with-current-continuation. They can be internally implemented with
function frame stack capture and switching. For other languages continuations
can be grafted on with continuation passing style, in which the functions
are modified to pass an additional parameter k
, the current continuation; the
return expressions are modified inside-out to make the evaluation order explicit
and finally k
is invoked. With CPS the function never returns, so in
non-tail-call languages a trampoline is additionally neccessary. A trampoline
repeatedly invokes passed-in functions in a loop to avoid the nesting of their
calls. With neither a trampoline nor tail call optimization stack overflow
occurs very soon.
continuation.js offers a CPS transform. CPS transforms are difficult in the sense that they turn functions inside-out and produce unreadable code. But in Javascript they are easier than in C because Javascript knows closures.
continuation.js also provides a trampoline, because Javascript doesn't have tail-call optimization.
NOTE! I am not sure whether this really works. It is just an idea which I need to test first.
One of the things which baffles Javascript newbies is the lack of a sleep() function and the difficulty to implement it in a good way. php.js' usleep() uses a busy loop to sleep. A very bad idea. I suspect that it is possible to implement it with continuations and a jump to the idle loop (with a continuation of the idle loop).
function sleep(seconds) {
var cc = new Continuation()
if (!cc instanceof Continuation) return /* sleep() ended */
setTimeout(cc, seconds * 1000)
Continuation.idle() /* go to the idle loop */
}
where new Continuation()
delivers a delimited continuation cc
whic can be invoked like this cc()
or passed to setTimeout
like a
function; and where cc instanceof Continuation
is true if the
continuation just has been created, i.e. a «goto» to the continuation
has not yet happened and finally where Continuation.idle
is a
continuation for the idle loop.
We see we can (perhaps) implement the control structure "sleep" with the help of continuations only. This also shows why continuations are important for Node.js: continuations can encapsulate complicated asynchronous control structures.
There is a CPS transform function. It takes a function or its source code and
applies a CPS transformation to it. There is also a trampoline and a function to
emulate Scheme's call/cc
, and also a few additional helper functions.
The CPS transform parses the function and walks the parse tree to replace the return expressions by their CPS transform: Do these steps:
Parse the function source code and produce a simplified parse tree (with
nodes relevant to the CPS transform: function call arguments; and in return
expressions either expressions expr
, nested CPS function calls
func
or operators op
).
Append the argument k
to the function's arguments.
For all return statements post-order walk and build the transformed expressions incrementally using these rules:
For an expression expr
empty
→ CPS(k, expr)
CPS(…, k)
→ CPS(…, function k(i++) { return CPS(k, expr) })
CPS(k, …)
doesn't occurFor a nested CPS function call func
empty
→ CPS(func, k)
CPS(…, k)
→ CPS(…, function k(i++) { return CPS(func, i, k) })
CPS(k, …)
→ CPS(func, …, k)
For an operator op
empty
doesn't occurCPS(…, k)
→ ??? TodoCPS(k, …)
→ CPS(k, node-left op node-right)
The CPS()
function calls are thunk trampoline invocations: the first
argument is the function to be called by the trampoline, the following
arguments its arguments. k
is either the first argument or the last or the
last argument is a function invoking k
directly or indirectly through the
trampoline. This ensures the thunk closure chain in which each thunk is the
current continuation exactly when the trampoline invokes the thunk.
Replace the return expression with the CPS transform incrementally built.
The idea of these incremental steps is to transform the return expression to
make the evaluation order explicit (and turn inside-out the expressions in that
process). For example in x + f(y + z)
y + z
is evaluated first, then f(y + z)
, then x + f(y + z)
. The post-order walks the nodes y + z
, f()
, +
in this sequence. I tried to generalize what I did when I wrote the CPS
transform by hand. It is a tough nut and a brain teaser and it is very easy to
get horribly confused. No wonder that the inventor of Unlambda included
call/cc
to make the brain teaser language intentionally difficult.
NOTE! I am not yet sure whether this algorithm really works.
Let's transform a recursive sum implementation just to show how it works. sum is not too complicated but yet nicely recursive.
function sum(n) {
if (n == 0) return 0
return sum(n - 1) + n
}
(I know that this is just n * (n + 1) / 2
, but bear with me.)
Append argument k
function sum(n, k) {
if (n == 0) return 0
return sum(n - 1) + n
}
Trivially transform return 0
function sum(n, k) {
if (n == 0) return CPS(k, 0)
return sum(n - 1) + n
}
Transform return sum(n - 1) + n
n - 1
sum()
+
empty
expr
is n - 1
:CPS(k, n - 1)
func
is sum
:
CPS(sum, n - 1, k)
expr
is n
:
CPS(sum, n - 1, function k(_0) { return CPS(k, n) })
op
is +
:
CPS(sum, n - 1, function k(_0) { return CPS(k, _0 + n) })
Conclude the CPS transformation
function sum(n, k) {
if (n == 0) return CPS(k, 0)
return CPS(sum, n - 1, function k(_0) { return CPS(k, _0 + n) })
}
Javascript has a complex grammar. There are Javascript parsers, so parsing is not a problem. More difficult is to simplify the parse tree so that it only contains CPS-relevant information. I plan to use PanPG of inimino.
The arguments
special object will most probably never be supported.
A few other constructs will never be supported (like perhaps function
expressions like (function() {})()
).
There is a lot of academic literature about CPS transforms. I don't understand them very well, so I am trying to solve the problem myself in my own words. I suppose with a bit of hard thinking and a little common sense this problem is really solvable in a non-academic way.