Skip to content

Exception Handling

Let's start by implementing a function inverses that takes as argument a list of numbers. If every number in this list is non-zero, then inverses returns the list of inverses of all the numbers in the input list. If there is a zero in the input list, then we cannot take its inverse. In this case, inverses aborts and return #f to indicate failure. #f represents False in Scheme. Note that Scheme is dynamically typed. Thus, it is fine that a function returns a list of numbers under certain conditions and a Boolean value under others.

Here's the definition of inverses:

(define (inverses xs)
  (define (invert abort)
    (lambda (x) (if (zero? x) (abort #f) (/ 1 x))))
  (call/cc (lambda (abort) (map (invert abort) xs))))

Let's take it apart: We define a function inverses with a single argument xs, the list of numbers to be inverted. The first thing we do in inverses is to define a local function invert. This function takes a continuation abort as argument. The result of (invert abort) is an anonymous function, defined using lambda, that takes a single argument x. If x is zero, we call the continuation abort with argument #f. I'll explain in a second what this does. Otherwise, we evaluate the expression (/ 1 x), which calculates the inverse of x, and this becomes the result of the anonymous function.1

inverses now calls an anonymous function using call/cc. The only argument of this anonymous function, named abort, is a continuation object that represents the computation after call/cc. The anonymous function now applies the function returned by (invert abort) to every element in xs using map. Let's assume first that all the elements in the list are non-zero. In this case, (invert abort) returns the inverse of each list element, so the result of (inverses xs) is the list of inverses of the elements in xs. (invert abort) never uses the continuation abort it was given as argument. If xs does contain a 0, then when encountering the first 0, (invert abort) invokes (abort #f). This aborts the remainder of the entire computation (lambda (abort) (map (invert abort) xs)), and call/cc returns with the value given to abort as the result. Here, we called (abort #f), so the result of call/cc, and thus of the entire invocation (inverses xs), is #f.

It is important to understand that calling (abort #f) doesn't only lead to the result of the call/cc expression being #f. The right way to think about it is that the computation calling abort had a future, a sequence of steps that it would still perform if it didn't call abort. This future is a continuation, albeit not one that we have an explicit object for. Calling abort replaces this continuation, this future of the computation, with abort. In particular, map would normally apply (invert abort) to the elements in xs that come after the first 0. Those applications never happen because they are part of the continuation that got replaced by calling abort.


  1. In Haskell, we would have defined invert to be a two-argument function, where the first argument is the continuation abort and the second argument is x. We would then obtain the equivalent of the anonymous function in this example by calling invert with only one argument. That's because functions in Haskell are curried. Scheme has no such thing as curried functions or partial function application. Thus, we need to simulate this by explicitly defining invert as a one-argument function that itself returns another one-argument function.