From 3ec198cc999b927eade3468eb25c337a54e2171e Mon Sep 17 00:00:00 2001 From: mehbark Date: Wed, 18 Feb 2026 23:37:01 -0500 Subject: [PATCH] ccgoto: pre-revision --- site-src/ccgoto.mdx | 282 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 276 insertions(+), 6 deletions(-) diff --git a/site-src/ccgoto.mdx b/site-src/ccgoto.mdx index 9416de4..ec4d0ba 100644 --- a/site-src/ccgoto.mdx +++ b/site-src/ccgoto.mdx @@ -1,21 +1,291 @@ --- 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 let’s use it to make goto. tags: post,short date: 2026-02-18 14:28:46 -5 --- + +In his 1968 letter, [A case against the GO TO statement](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 one’s program.” +Unfortunately, scheme programmers aren’t given that invitation. +That’s 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 let’s briefly review. +Perhaps you’ve seen a BASIC program that looks something like this: ```basic 10 PRINT "Hello, world!" 20 GOTO 10 ``` -Here is some `inline code`. +This, as you may have guessed, outputs: -```js -console.log("Surely, javascript is supported"); +```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.) + +You’re 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 -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. +Using `goto` here let’s 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 can’t leave the function, but otherwise it is substantially similar to BASIC’s `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?” +Let’s 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 don’t 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 scheme’s multiple return values; `(values v1 v2 ...)` is just `(call/cc (lambda (k) (k v1 v2 ...)))`. + +I recommend reading about continuations in Dybvig’s [The Scheme Programming Language](https://www.scheme.com/tspl4/further.html#g63) +if you’re (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 let’s 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 ...))])) +``` +Let’s 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! +... +``` + +Here’s 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 +``` + +I’ll 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 don’t 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 let’s 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 aren’t like `C` labels at all. +Well, remember how I said that continuations don’t necessarily come back to where you called them?” +We’re 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 aren’t 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.