twopn/site-src/ccgoto.mdx
2026-02-18 23:37:01 -05:00

292 lines
9.5 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: Emulating GOTO in Scheme with continuations
description: GOTO sucks and is evil and I hate it, but what if there were parentheses? `call/cc` is kinda like goto, so lets use it to make goto.
tags: post,short
date: 2026-02-18 14:28:46 -5
---
In his 1968 letter, [<i>A case against the GO TO statement</i>](https://www.cs.utexas.edu/~EWD/transcriptions/EWD02xx/EWD215.html)
(known only by that name), Dijkstra said “[t]he go to statement as it stands is just too primitive, it is too much an invitation to make a mess of ones program.”
Unfortunately, scheme programmers arent given that invitation.
Thats no fair!
Fortunately, scheme has a procedure, `call/cc`, that we can use to emulate the control flow that `GOTO` provides.
We can use syntactic abstraction to invite our scheme programmers to make a mess of their programs in a limited context.
{/*TODO actually complete intro*/}
{/*todo revision*/}
## How `GOTO` works
Odds are, you know how `GOTO` works, but lets briefly review.
Perhaps youve seen a BASIC program that looks something like this:
```basic
10 PRINT "Hello, world!"
20 GOTO 10
```
This, as you may have guessed, outputs:
```text
Hello, world!
Hello, world!
Hello, world!
Hello, world!
...
```
…forever.
Normally, control proceeds from the lowest line number to the highest line number, but the `GOTO` statement “jumps” to the given line, no matter where it is.
(Forgive my imprecision, this is not a BASIC tutorial.)
Youre more likely to see `goto` in `C`:
```c
void do_something() {
char *important_stuff = (char*)malloc(/* ... */);
FILE *important_file = fopen(/* ... */);
// do stuff...
if (errno != 0) goto cleanup;
// do more stuff...
if (errno != 0) goto cleanup;
printf("Success!\n");
// control falls through even if everything goes well
cleanup:
free(important_stuff);
fclose(important_file);
}
```
Using `goto` here lets us avoid repeating the `cleanup` logic.
Not my thing, but this is what `goto` fans like.
In `C`, `goto` uses `labels:` instead of line numbers, and it cant leave the function, but otherwise it is substantially similar to BASICs `GOTO`.
Hopefully you understand `goto` now. It lets you jump around.
The second thing you need to understand before we can implement `goto` with `call/cc` is how `call/cc` works.
## How `call/cc` works
`call/cc` is short for `call-with-current-continuation`.
Oh, you wanted more explanation? Ugh, fiiiine.
A certain smart guy once said that “[i]f you can't explain it simply, you don't understand it well enough.”
So, let's see if I understand `call/cc` well enough.
`call/cc` takes one argument, a procedure, and returns the result of applying that procedure with the current continuation as an argument.
What is “the current continuation?”
Lets start with an example.
```scheme
(define cont #f)
(begin
(+ 1 (call/cc
(lambda (k)
(set! cont k)
0)))
(display "The number is: ")
(write (cont 41))
(newline))
```
If we run this program, `cont` will be a procedure that adds `1` to its argument.
Seems useless, but let's run it anyway. It outputs:
```
The number is: The number is: The number is: ...
```
…forever‽
Continuations are a lot like procedures, but they dont necessarily come back to where you called them.
`cont` is actually something like
```scheme
(define cont
(lambda (x)
(+ 1 x)
(display "The number is: ")
(write (cont 41))
(newline)))
```
In this form, the unconditional recursion is obvious.
The `k` that `call/cc` calls its argument with represents, roughly, the rest of the computation.
The “current continuation” is what will be executed next at the point that `call/cc` is called.
Incidentally, this helps me understand schemes multiple return values; `(values v1 v2 ...)` is just `(call/cc (lambda (k) (k v1 v2 ...)))`.
I recommend reading about continuations in Dybvigs [<i>The Scheme Programming Language</i>](https://www.scheme.com/tspl4/further.html#g63)
if youre (justly) dissatisfied with my explanation or just want to learn more about how they work and their applications.
We talked about how `call/cc` works, so lets finally use it to implement `goto` in scheme!
## `goto` in scheme
Here you go:
```scheme
(define-syntax with-goto
(syntax-rules ()
[(_ goto rest ...)
(let ()
(define goto #f)
(%labels rest ...)
(call/cc
(lambda (k)
(set! goto
(lambda (label) (k (label))))
rest ...)))]))
(define-syntax %labels
(syntax-rules ()
[(_) (begin)]
[(_ (_ ...) rest ...) (%labels rest ...)]
[(_ label rest ...)
(begin
(define (label) rest ...)
(%labels rest ...))]))
```
Lets run that with our favorite [R⁶RS](https://www.r6rs.org/) implementation (mine is [Chez Scheme](https://cisco.github.io/ChezScheme/)):
```scheme
(with-goto goto
loop (display "Hello, world!\n")
(goto loop))
```
```text
Hello, world!
Hello, world!
Hello, world!
Hello, world!
...
```
Heres an example that doesn't loop forever:
```scheme
(let ([x 1])
(with-goto go
(go loop)
double
(set! x (* 2 x))
loop
(display x) (newline)
(when (< x 1000)
(go double))
(display "done\n")))
```
It outputs:
```scheme
1
2
4
8
16
32
64
128
256
512
1024
done
```
Ill explain this macro one part at a time.
First, `(define-syntax goto (syntax-rules () [...]))` defines `goto` as a syntax transformer (more precise name for a macro) using the `syntax-rules` pattern-matching language.
The `()` after `syntax-rules` is the empty list of literals; we don't have any special words here, so it doesn't apply.
You can read more about how `syntax-rules` works in [TSPL](https://scheme.com/tspl4/syntax.html#./syntax:s14), but we'll only be using the most basic features here.
The important thing is to know that matched names are replaced in the output and that `x ...` matches/splices zero or more expressions.
Also, `syntax-rules` is hygienic, so dont stress about name collisions.
Then, we match `(_ goto rest ...)`.
Anything else is a syntax error.
The `_` is for `with-goto` (we do not want to repeat ourselves).
We output a big `let` expression.
Notice how the second example uses `go` instead of `goto`?
That's because the first element in `with-goto` is the name of the `goto` procedure.
We `define` it as false because we will set it later.
Next, we pass the body (`rest ...`) to `%labels`, which deserves its own heading.
## Extracting labels
`%labels` is a syntax transformer with three cases:
1. `(_)` Nothing is passed: `(begin)` (do nothing)
2. `(_ (_ ...) rest ...)` A list is passed: Ignore it and process `rest ...`. We treat expressions of the form `(x ...)` as statements, not labels.
3. `(_ label rest ...)` Finally, a label!
When we encounter a label, we define a thunk (procedure that takes no arguments) with the rest of the arguments as its body, like so:
```scheme
(define (label) rest ...)
```
Putting it all together,
```scheme
(%labels
a
(display 1)
b
(display 2)
c
(display 3))
```
(morally) expands to
```scheme
(begin
(define (a)
(display 1)
b
(display 2)
c
(display 3))
(define (b)
(display 2)
c
(display 3))
(define (c) (display 3)))
```
(The leftover labels have no effect)
This helper on its own is a really crappy way to define functions with shared tails, so lets bring it all together.
## Going to
We have our labels as functions, but what for?
If we call these procedures, they will return control to us, so they arent like `C` labels at all.
Well, remember how I said that continuations dont necessarily come back to where you called them?”
Were going to exploit that property to implement `goto`.
We wrap the body of `with-goto` in `(call/cc (lambda (k) ...))`.
Now, inside the body, if we call `k`, instead of continuing execution, we'll immediately stop.
By calling a label before `k`, we effectively jump from whatever we were doing to whatever follows the label.
This is exactly the behavior we were looking for!
`(set! goto (lambda (label) (k (label))))` makes `goto` do exactly this (function arguments have to be evaluated before the procedure call takes place).
We use `(define goto #f)` combined with a `set!` because the labels we defined earlier need to be able to see the `goto` function.
This is what our first `with-goto` looks like when we expand it:
```scheme
(let ()
(define goto #f)
(define (loop) (display "Hello, world!\n") (goto loop))
(call/cc
(lambda (k)
(set! goto (lambda (label) (k (label))))
loop
(display "Hello, world!\n")
(goto loop))))
```
(It is in fact expanded slightly differently and more efficiently, it does not use unbounded stack space afaik, which makes sense because we arent actually increasing the depth of the callstack when we `goto`.)
## Conclusion
This is useless.
There are a lot of cool things that you can implement with `call/cc`, but this is dumb!
There is a *lot* of nonsense that you can do with this implementation (try messing with nested `with-goto` or storing `goto` elsewhere).
Still, I hope you learned a bit about `call/cc` and what building abstractions with it can look like.
Unfortunately, [`call/cc` sucks](https://okmij.org/ftp/continuations/against-callcc.html)!
This has been known for decades!
[Delimited continuations](https://en.wikipedia.org/wiki/Delimited_continuation) are way better!
Use the [`⁻Ƒ⁻` operator](https://web.archive.org/web/20250112082613/https://legacy.cs.indiana.edu/~dyb/pubs/monadicDC.pdf)!
Thanks for the soapbox.