As Alex wrote. But let me elaborate this, as someone who has played with these things for close to 40 years.
Continuations come in two forms to manipulate the flow of control in programs:
— manually maintained versions, usually via anonymous functions (or objects), with occasional language support (monads in Haskell)
— language-supported objects, usually via control operators such as `call/cc`, `prompt`,` or `control` (see racket/control)
Thirty years ago, OS researchers noticed that they were manipulating flow of control via manually maintained continuations (via function pointers and records); indeed they were maintaining prompt-delimited continuations. Twenty years ago, a widely spread example of the first one was found in web scripts using the CGI interface. For the past decade, JavaScript programmers had to manually construct continuations to resume “processes” after an event got handled.
People trained in the use of control operators recognize this pattern immediately and, in a language that support control operators, can build abstractions that eliminate the error-prone, manual maintenance of lambda-coded continuations. The PLT Web Server is an example of a system that supports a carefully designed abstraction layer that eliminates the CGI induced manual continuation problem. The Mach 4 operating system is an example that relies on dealing with delimited continuations. Their use of C made it somewhat difficult to build the abstraction properly, though setlong jmp and such tools make it possible. (The group moved to MS from CMU as it worked on this OS, and they did use the ideas — unsuccessfully, from what I understand — in a release of Windows in the early 00s.)
Once people in design-oriented PLs figure out a good abstraction, other language implementors tend to adopt some version of it. This is how Python got generators. It’s how JS gets new features all the time (see async
).
This is and should be the way new abstractions come about. If you think of the design space of sequential languages as a multi-dimensional space — with concepts such as binding, state, and control as the dimensions — then operators such as set!
and call/cc
form a basis. Just like people working with vector spaces, PL designers should not use the basis that randomly came about but the basis that is good for their problem. Sadly people have very little imagination of how to abstract over state manipulation, and thus set!
and its := = <- friends are still around (probably due to our friends in Algorithms). The control world is much richer than that (probably because the same friends don’t really care about flow of control). You can find threads (pre-emptive and scheduled), coroutines, signal handing systems, generators, and so on in many PLs — and all of them can be implemented with call/cc
and its colleagues. The Web and mobile gateways are likely to generate more such abstractions, but it will take some time for the dust to settle.
Just because a feature can be implemented as a combination of others does not mean, however, that it should be done this way. Direct (often called “primitive” support) gives compiler writes much better ideas for optimizing passes. As some have noticed, call/cc
is (conceptual and performant) overkill for function return
s; use let/ec instead.
For call/cc
, Chez Scheme supports this feature with the best asymptotic cost in the Scheme world. SML/NJ’s approach is cheaper still, if you embrace the idea that memory is dirt cheap and essentially infinite. (I don’t know whether even Andrew still accepts this.) Racket’s control library is an example of how a pretty-cheap call/cc
can become a somewhat more expensive call/cc
on the language that runs on top of it. Racket/BC demonstrates just how expensive call/cc
is if you think compiling thru C with the stack intact is a good idea.
Perhaps this helps — Matthias