diff --git a/site-src/ccgoto.mdx b/site-src/ccgoto.mdx index ec4d0ba..1893ca9 100644 --- a/site-src/ccgoto.mdx +++ b/site-src/ccgoto.mdx @@ -5,19 +5,20 @@ tags: post,short date: 2026-02-18 14:28:46 -5 --- +export function Basic() { + return BASIC; +} + 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*/} +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 let’s briefly review. -Perhaps you’ve seen a BASIC program that looks something like this: +Perhaps you’ve seen a 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 tutorial.) You’re more likely to see `goto` in `C`: @@ -60,20 +61,15 @@ cleanup: } ``` -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`. +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 can’t leave the function, but otherwise it is substantially similar to ’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 @@ Let’s 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. +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 @@ -111,15 +106,52 @@ Continuations are a lot like procedures, but they don’t necessarily come back ``` In this form, the unconditional recursion is obvious. +Continuations are a lot like procedures, but they don’t 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, it’s like we ran the same code but chose another value—this +is the principle that underlies the +ambiguous choice +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 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. +if you’re (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 let’s finally use it to implement `goto` in scheme! +We now have a decent understand of how `call/cc` works, so let’s 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 ...))])) ``` -Let’s run that with our favorite [R⁶RS](https://www.r6rs.org/) implementation (mine is [Chez Scheme](https://cisco.github.io/ChezScheme/)): +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") @@ -257,11 +289,10 @@ Well, remember how I said that continuations don’t necessarily come back to wh 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! +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 aren’t 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 AFAIK, which makes sense because we aren’t 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. + +Here’s 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 [Handling Control](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.22.7256), a 1993 article in Programming Language Design and Implementation.