What's call-with-current-continuation?

A tutorial for Elisp hackers

If you know Elisp, you know catch and throw:

(defun member-p (x l)
  (catch 'exit
    (mapc #'(lambda (y)
	      (if (equal x y)
		  (throw 'exit t)))
	  l)
    nil))

Essentially, catch establishes a labelled exit point. Calling throw with the label within the extent of corresponding catch effectively makes the catch return right away, without waiting for any further evaluation. From a more low-level standpoint, throw just unwinds parts of the stack up to the activation record for the corresponding catch.

The important restriction is that throw only works within the extent of the catch. Once catch has returned, a throw to its label fails, provided there is not another catch lurking with the same label. From an implementation standpoint, this is natural.

Now, imagine first a syntactically different mechanism equivalent to catch and throw where the above code would look like this:

(defun member-p (x l)
  (let-throw exit
    (mapc #'(lambda (y)
	      (if (equal x y)
		  (funcall exit t)))
	  l)
    nil))

What we've done is, instead of using an arbitrary label to connect catch and throw, we just have a new syntactic form let-throw which binds a name to a function which subsequently performs the throw. (Exercise: Implement let-throw as a macro in Emacs Lisp.)

Now, let's say you dislike macros for some reason, but would like something equivalent to let-form. The primitive binding mechanism in Emacs Lisp is abstraction, so it might look like this:

(defun member-p (x l)
  (call-with-throw
   #'(lambda (exit)
       (mapc #'(lambda (y)
		 (if (equal x y)
		     (funcall exit t)))
	     l)
       nil)))

The call-with-throw function expects a one-argument function as its argument, and calls it right away, passing a function which performs the throw. This last explanation sounds complicated because of the multiple occurrence of the word "function", but really nothing fancy is going on.

Now, after the syntactic changes, let's change the semantic viewpoint. We've said that throw unwinds part of the stack, throwing some top part of it away. This means that, after the throw, the stack looks exactly as it looked at the time of the catch.

(That's not entirely true, as set may have mutated some of the bindings. However, the shape is the same---the same return addresses etc. etc.)

So, a slightly more brutal description of throw would be that it replaces the current stack with the catch's stack.

Now, what exactly is a stack? In traditional programming language implementations, it tells the execution engine what to do after the current function call returns. In effect, it represents the future of the computation. In the lambda calculus, where Lisp ultimately comes from, we don't talk of calls and returns, we talk of abstractions and applications. There, the future of an application (a function call) is simply the expression surrounding it, often called a context of continuation.

Normally, continuations work behind the scene, as the stack grows and shrinks. What let-throw and call-with-throw actually do is, they turn the stack (a meta object, really) into a Lisp object, that, when called, throws away the current stack and replaces it by the saved value. The restriction that the throw must happen within the extent of the call-with-throw means that a stack pointer is absolutely sufficient to represent the continuation.

Now, what about the following, totally nonsensical code:

(defvar x nil)
(call-with-throw
  #'(lambda (exit) (setq x exit)))
(funcall x 'yo-dude)

In Emacs Lisp, we have made a mistake by calling the value of exit outside the extent of its call-with-throw form. Imagine, for a second, that it would not be mistake, but that calling x could magically restore the stack that's been lost. This would mean that call-with-throw would return again, leading to an endless loop. Not exciting or useful, but it's at least possible to see what happens.

With this generalization, it makes sense to rename call-with-throw to call-with-current-continuation, as the new semantics are strictly more powerful.

The intuitive question is if call-with-current-continuation is useful for anything but producing expensive endless loops. (Of course, it's good for anything catch and throw are good for, but is its additional generality useful for anything?)

Hopefully, nobody'll kill me when I switch to Scheme notation. Remember things are lexically scoped, (define (f ...) ...) is the same as (defvar f (...) ...), funcall is unnecessary, set! is like setq, #t is "true" and #f is "false".

A frequently useful abstraction is the concept of a coroutine: Several threads run in the system, but, as opposed to threads, they transfer control among each other explicitly. Each coroutine has its own computation going, so obviously each has its own future/continuation. A coroutine is an object created by the new-coroutine procedure:

(define (new-coroutine)
  (let ((future 'dummy))
    (lambda (action)
      (if action
	  (set! future action)
	  (future #f)))))

Thus, a coroutine is a procedure which is passed a message called action. (Note the procedure is lexically closed over future. This won't work in Emacs Lisp.) The message can be:

Now, a coroutine needs to know what's supposed to happen once its running. Let's send it a message:

(define (init-coroutine! coroutine action)
  (coroutine (lambda (dummy)
               (action))))

(The dummy argument will always be bound to #f by the coroutine.) Now, the only remaining question is how control can pass between two coroutines. Let's say, the from coroutine is running, and control is to transfer to to. We need to remember how from can continue execution by getting its continuation and remembering it in the coroutine. Then, we can ask to to take control:

(define (transfer! from to)
  (call-with-current-continuation
   (lambda (c)
     (from c)
     (to #f))))

There are many other applications of call-with-current-continuation. Hopefully, this will have shed some light on the issue.
Michael Sperber [Mr. Preprocessor]
Last modified: Wed Aug 6 21:32:14 MST 2003