Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
An argument against call/cc (2012) (okmij.org)
76 points by luu on Nov 30, 2014 | hide | past | favorite | 13 comments


FWIW, the behavior of call/cc, et. al., is implementation dependent, and memory leakiness varies considerably.

I tried the "memory leak" tests in Chicken (4.9.1) using the CSI REPL. The only modification made to the example "gen-leak.scm" was uncommenting the (display ...) line in (print-stream ...).

The result with (print-stream #f (gen-stream ones)) was non-terminating production of "1", like an infinite loop, but it didn't run out of memory, even after running >10 minutes in a resource-constrained VM.

I'm not familiar enough with how call/cc is implemented in Scheme48 vs. Chicken to comment in detail, but I'm sure there are substantial differences. The Chicken site (http://call-cc.org) provides quite a bit of info about its own implementation.


If you generalize labels so that they become first class, then call/cc can be written in terms of goto. http://ssp.impulsetrain.com/goto.html


It's longjmp.


not quite. It is similar to longjmp except that this can jump down the stack. these labels can be jumped to even after the function returns


Right. The way it was explained to me years ago that really stuck with me is that one way to implement call/cc is to copy the stack onto the heap.

(And another way to do it is to simply allocate all your function call frames on the heap to begin with and not have a stack. Then call/cc is essentially free, but you need a really good incremental garbage collector because now every function call conses.)


Does copying the stack really work? What if some local variable changes in between the call/cc and calling the continuation? If the stack is a copy, that change will not be reflected when the continuation runs.


That's a very interesting point. AFAICT, the Scheme standard doesn't actually say what should happen in this case. But here's what e.g. Chibi scheme does:

    > (let ((x 0)) (list (call/cc (lambda (f) (set! x 1) (f 2))) x))
    (2 0)


The equivalent in Oort produces:

    localhost:~/oort/examples% ../oort callcc2.nl 
    2
    1
as expected since Oort uses heap allocated activation records.

The Oort program:

    typedef continuation_t = fn (p: int32) -> void;
    
    call_cc (fun: fn(k: continuation_t) -> void) -> int32
    {
        retval: int32;
    
        fun (fn (v: int32) -> void {
            retval = v;
            goto current_continuation;
        });
        
        // If fun returns without calling the continuation,
        // then just return 0
        return 0;
    
    @current_continuation:
        return retval;
    }
    
    x: int32 = 0;
    
    print call_cc (fn (k: continuation_t) {
        x = 1;
        k (2);
        });
    
    print x;


Delimited continuations are better. Guile has them, and they rock. It's very easy to implement coroutines and other control flow structures with them.



This is covered in the article


I wonder how much that paper influenced guile delimited continuations support.


This seems a bit ironic to see this posted on one of the few sites on the internet that use continuations as it's primary control flow mechanism.




Consider applying for YC's Summer 2026 batch! Applications are open till May 4

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

Search: