Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Call/cc for C programmers (2012) (schemewiki.org)
62 points by fao_ on Aug 15, 2018 | hide | past | favorite | 29 comments


This is fascinating, but it seems like it only applies to "one shot" programs, like compilers. How applicable is this in the web application space, where state is usually stored externally to the process itself? If used in the way pg's Arc was intended for making web sites easier to reason about developing, then stopping your server results in losing context for follow-up requests. Even modern desktop and mobile apps have a concept of serializing the user's current context in case the computer accidentally shuts down. Context state can't be used safely with call/cc in these situations.


There are continuation based server-side web frameworks that will serialize & restore the continuation from storage between web requests. Eg Seaside. Rhino can also serialize continuations, there might be a web framework for it too.

For frontend, there are some nontrivial transformations needed to compile continuation based code for a JS target. You can't use the native stack, etc. (Same for WebAssembly I gather, judging from the recent Go/WebAssembly implementation discussion)


I seem to remember Cocoon was using continuations, and there was a framework for ruby inspired by seaside but I don't know what happened to it.

To add to what you wrote, the elevator pitch of Seaside and the likes is that we basically write web pages as a sea of GOTO statements and global state, the use of continuations allows us to get to the "structured programming and function calls" level.


Not quite true. You can serialize a closure. Emacs lisp does so in a pretty interesting way: a closure is just `(closure ((a . 1) (b . 2)) (lambda (x) (+ x a b)))`.

The point is, if you can serialize closures, I think you can serialize continuations.


And how does this work if it involves an open file handle, or some other non serializable state?




I think HN used continuations


The explanation for call/cc has always been extremely roundabout and confusing to me, especially since setjmp/longjmp aren't themselves typically used in C code.

Here's a hopefully simple explanation: (call/cc f) means "call f and pass it a customized 'return' procedure, which, when invoked with your desired return value, would make this call/cc invocation return that value right here and continue executing the program".


Steps to understanding call/cc (for C programmers):

    0. recognize that function return in C (and many langs)
       is just like implicitly calling a function of one
       argument (the return value)
    1. further recognize that these "return functions" are
       *closures*!  on the stack they are passed as both,
       an instruction pointer *and* the frame pointer of the
       frame into which to return!
    2. add closures (somewhat like GCC local functions)
    3. make a builtin macro-like thing that lets you call
       a C function with an alternative "return-function"
       argument that is like any other function (of one
       argument), and particularly allows local function
       references to be given as a return-function
    4. make a builtin macro-like thing to get at the current
       frame's return-function "argument" -- i.e., reify
       the previously implicit current continuation
    5. screw the function call stack, change the code
       generator and run-time to allocate function frames
       on a heap (and add garbage collection)
Presto, you have call/cc.

Shorter: allocate function frames on the stack, reify the return function argument closure and call it the continuation. To "reify" means to give reality to something that was previously hidden / implied.

call/cc looks really awesome until you realize that it has / causes problems. One problem is that if you hold on to a reference to a continuation then you're actually preventing GC of all the stack frames captured by that continuation. Then there are thread safety issues... Any language that has variables (as opposed to lexical, immutable bindings) will have thread safety problems. Imagine using closures as callbacks, and having said closures change variables they close over, and now imagine them racing against each other to modify the same variables. Well, in a Scheme continuations are strung-up closures, so now imagine multiple threads trying to call the same continuation. These thread-safety problems run deep, and will tend to push one towards Haskell or Rust.

Really, call/cc is a bit of a parlor trick :) -- a fascinating parlor trick, but still a parlor trick.

If you want much much more detail, I recommend two great books that deal with this subject:

     - On Lisp (by Paul Graham)
     - Lisp In Small Pieces
In On Lisp, Paul Graham has a chapter where he builds a set of macros that give you Scheme-style continuations with minimal compromises in how you write Lisp code.

Lisp In Small Pieces is all about writing Lisp/Scheme interpreters and compilers, so it touches on all of this depth and great detail.


I should also add that though call/cc is a parlor trick, continuation passing style (CPS) is not. Making continuations explicit is a standard technique in compiler design, and it pays off handsomely.

Also, while i'm at it, call/cc can be used to implement co-routines, which is fine enough, though coroutines can also just be provided by the language anyways. It is elegant that you can get co-routines as simple function calls rather than code that has to save/restore registers.


> Shorter: allocate function frames on the stack, reify the return function argument closure and call it the continuation. To "reify" means to give reality to something that was previously hidden / implied.

Oops, s/stack/heap/.


Simple, but missing many parts. For example, what happens to variables on the stack? The heap? What does it mean to continue execution from “here” — from this point in time? Does that mean file handles will be closed or re-opened to match the current state of the program?

These questions usually have straightforward answers, but some are counterintuitive.


What do you mean what happens to them? Wouldn't they just be kept alive with the same semantics as how a variable used in a nested function is kept alive? Or is there more that I'm not aware of? If that's all there is to it, the semantics should be second nature if you've done Java, Python, or any other GC'd language, right?


That’s one option. But the unfortunate conclusion of that choice is that continuations leak a lot of memory. One shot continuations are better, since it frees up the variable references.


I don't see how this is different from lambdas in general, in JS/Python/etc... wouldn't it only be held as long as the variables are alive? If you use the callback only once then they should be eligible to be freed after being used once.


http://okmij.org/ftp/continuations/against-callcc.html is pretty informative. It was kind of eye opening to see just how many corner cases there are when dealing with continuations.

(See the section “memory leaks”.)


Thanks for the link! Unfortunately I don't understand what it's saying the issue is... would you have a simple example to illustrate? It's been years since I did Scheme, I've never actually needed to use call/cc in a program, and I'm not advanced enough in Scheme to have seen shift/reset (as with call/c, their documentation is not quite clear). :\


I could, but not at 3 AM. :) I'll try to remember tomorrow.



This brings back memories. In high school I helped out with the launch of this wiki.


Site is down.


It does seem to fail quite a lot - so much that I bookmarked the wayback machine which has a good deal of it archived.

[1] https://web.archive.org/web/20180128095717/http://community....


archived https://archive.fo/wGHY 6 years ago


Link says (2017), ironically.


We'll change it.


The link says 2017 because the content was listed by the site as updated in 2017.


Can't access the link.


> Once you've got a continuation object, calling it as say (cont 123) is like a C code longjmp(jbuf, 123)

> In Scheme of course any object can be "returned" in this way (and even values? for more than one object), not just a single int.

I think the author's last experience with C might be a bit dated.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: