ccgoto: done

This commit is contained in:
mehbark 2026-02-19 04:05:09 -05:00
parent 3ec198cc99
commit a36fd874ec
Signed by: mbk
GPG key ID: E333EC1335FFCCDB

View file

@ -5,19 +5,20 @@ tags: post,short
date: 2026-02-18 14:28:46 -5
---
export function Basic() {
return <abbr title="Beginners All-purpose Symbolic Instruction Code">BASIC</abbr>;
}
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*/}
We can use syntactic abstraction to invite scheme programmers to make a mess of their programs in a limited context.
## 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:
Perhaps youve seen a <Basic/> program that looks something like this:
```basic
10 PRINT "Hello, world!"
20 GOTO 10
@ -35,7 +36,7 @@ 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.)
(Forgive my imprecision, this is a basic tutorial, not a <Basic/> tutorial.)
Youre more likely to see `goto` in `C`:
@ -60,20 +61,15 @@ cleanup:
}
```
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`.
Using `goto` here lets us avoid repeating the `cleanup` logic.
Not my thing, but this is what most `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 <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?”
@ -91,15 +87,14 @@ Lets start with an example.
```
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:
You might call this example contrived.
That is because I contrived it to be an example.
Lets 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
@ -111,15 +106,52 @@ Continuations are a lot like procedures, but they dont necessarily come back
```
In this form, the unconditional recursion is obvious.
Continuations are a lot like procedures, but they dont necessarily come back to where you called them.
This example demonstrates that difference in behavior:
```scheme
(define (displayln obj)
(display obj)
(newline))
(define cont #f)
(displayln
(call/cc
(lambda (k)
(set! cont k)
"cont set")))
(begin
(displayln "procedure called")
(displayln "after procedure call")
(cont "continuation called")
(displayln "after continuation call"))
```
This outputs
```text
cont set
procedure called
after procedure call
continuation called
```
Notice how after calling a procedure, in this case `displayln`, the output continues but not after calling `cont`.
When we call `cont` with a new value, its like we ran the same code but chose another value—this
is the principle that underlies the
<a id="Sitaram-link" href="https://ds26gte.github.io/tyscheme/index-Z-H-16.html#TAG:__tex2page_sec_14.1"><code>amb</code>iguous choice</a>
operator.
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.
if youre (justly) dissatisfied with my explanation or just want to learn more precisely how they work and their applications.
We talked about how `call/cc` works, so lets finally use it to implement `goto` in scheme!
We now have a decent understand of how `call/cc` works, so lets finally use it to implement `goto` in scheme!
## `goto` in scheme
Here you go:
@ -145,7 +177,7 @@ Here you go:
(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/)):
Lets run that with our favorite [<abbr title="Revised Revised Revised Revised Revised Revised Report on the algorithmic language Scheme">R⁶RS</abbr>](https://www.r6rs.org/) implementation (mine is [Chez Scheme](https://cisco.github.io/ChezScheme/)):
```scheme
(with-goto goto
loop (display "Hello, world!\n")
@ -257,11 +289,10 @@ Well, remember how I said that continuations dont necessarily come back to wh
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!
Inside the body, if we call `k` like `(k (label))` we effectively replace the body with the result of calling `label`.
We accomplished a jump!
`(set! goto (lambda (label) (k (label))))` makes `goto` do exactly this (function arguments have to be evaluated before the procedure call takes place).
`(set! goto (lambda (label) (k (label))))` makes `goto` do exactly this (note that 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:
@ -276,7 +307,19 @@ This is what our first `with-goto` looks like when we expand it:
(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`.)
It is in fact expanded slightly differently and more efficiently, it does not use unbounded stack space <abbr title="As Far As I Know">AFAIK</abbr>, which makes sense because we arent actually increasing the depth of the callstack when we `goto`.
To demonstrate that fact and that labels are values, here is one last program.
Make an educated guess about what it does before running it, and see if you can make any general statements about its output (other than “the output never ends”).
```scheme
(with-goto go
a
(display "A")
b
(display "B")
(go (if (zero? (random 2)) a b)))
```
## Conclusion
This is useless.
@ -284,8 +327,14 @@ There are a lot of cool things that you can implement with `call/cc`, but this i
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)!
## Further reading
`call/cc` is awesome, but unfortunately [it 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)!
Consider the [`⁻Ƒ⁻` operator](https://web.archive.org/web/20250112082613/https://legacy.cs.indiana.edu/~dyb/pubs/monadicDC.pdf)!
Thanks for the soapbox.
Heres a satisfying full-circle:
[Earlier](#Sitaram-link), I linked to an implementation of `amb` by Dorai Sitaram.
I recognized the name because Racket implements [the two operators](https://docs.racket-lang.org/reference/cont.html#%28form._%28%28lib._racket%2Fcontrol..rkt%29._~25%29%29)
he describes in [<i>Handling Control</i>](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.22.7256), a 1993 article in <i>Programming Language Design and Implementation</i>.