ccgoto: pre-revision

This commit is contained in:
mehbark 2026-02-18 23:37:01 -05:00
parent e39ce0dbca
commit 3ec198cc99
Signed by: mbk
GPG key ID: E333EC1335FFCCDB

View file

@ -1,21 +1,291 @@
--- ---
title: Emulating GOTO in Scheme with continuations 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 let's make goto. 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 tags: post,short
date: 2026-02-18 14:28:46 -5 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 ```basic
10 PRINT "Hello, world!" 10 PRINT "Hello, world!"
20 GOTO 10 20 GOTO 10
``` ```
Here is some `inline code`. This, as you may have guessed, outputs:
```js ```text
console.log("Surely, javascript is supported"); 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);
}
``` ```
```ruby Using `goto` here lets us avoid repeating the `cleanup` logic.
3.tap.tap.tap.tap.tap.tap.tap.tap.tap.tap.tap.tap.tap.tap.tap.tap.tap.tap.tap.tap.tap.tap.tap.tap.tap.tap. 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.