← Back to context

Comment by taeric

1 day ago

I confess I like Common Lisp's TAGBODY far more than I feel like I should. Having constrained GOTO semantics to a short section of the codebase is surprisingly useful.

Following the recommendations of Knuth, the language Mesa, which was implemented at Xerox during the seventies, and which was a source of inspiration for various later languages, including Modula, Ada and Python, included a form of "restricted GOTO" which is the most useful kind of GOTO in my opinion.

The Mesa restricted GOTO allowed jumping forwards, but not backwards, and it allowed jumping towards an outer block, but not towards an inner block.

These 2 restrictions eliminate all the "harmful" features of the traditional GOTO, while retaining its advantages for handling exceptional conditions or for terminating multiple levels of nested program structures.

The Common Lisp TAGBODY appears to be only partially restricted, by allowing backward jumps, so it does not prevent the kind of hard-to-understand program structures for which GOTO was criticized.

GOTOs in random directions may be used to implement state machines, but such state machines can still be implemented in a language with restricted GOTO by not using GOTO, but by using mutually recursive procedures, if tail-call optimization is guaranteed.

  • I'm not clear that jumping backwards is that tough to reason with. Notably, Knuth's algorithms do that quite commonly, right?

    I do think they need to be somewhat constrained to not jump to places that need new things initialized. Which, it is truly mind blowing to know folks used to just jump straight into other functions. Mid function. Because why not.

    • Knuth's algorithms do that because they are written in assembly language.

      In assembly language you must use backwards jumps to implement loops.

      However, good assembly language programmers do not use arbitrary backwards jumps, but they use only a certain number of patterns, which correspond to the various kinds of loops that are also encountered in high-level programming languages.

      Many programming languages are somewhat incomplete, because they do not have all the kinds of loops that exist in other programming languages. When programming in an assembly language, a good programmer will not restrict the loops to only the kinds of loops that are available in C/C++, but the non-nested loops that are possible with arbitrary GOTO will not be used.

      The best practice in assembly programming is to not use explicit backwards jumps, but to define macros for different kinds of loops, then use the macros, which make the code look exactly like in a high-level programming language.

      Knuth's algorithms do not use macros, like in real assembly programming, because their purpose is to show you an actual implementation, not a higher-level abstraction.

      1 reply →

From John Cowan:

TAGBODY doesn't actually require continuations, delimited or undelimited, just proper tail calling. A macro can rewrite each section of the TAGBODY into a procedure nested within a `let` that tail-calls its successor, and the body of the `let` tail-calls the first procedure. (GO tag) is then equivalent to just (tag). This is a great way of doing state machines. Chicken has a tagbody egg, I think.

Constrained GOTO semantics sounds a lot like delimited continuations. Indeed I think Scheme continuations are a little too powerful for regular use by having the possibility of global effect (like longjmp). Delimited continuations make the effect more local.

  • Delimited continuations always bounced off of me. In theory, they should be a lot like coroutines? I think, in practice, I just never really internalized all that goes into managing the current "environment" for a piece of code that is managed by the call state.

    Like, I have a few partial mental models for everything that they pull together. I haven't really tried to build on that, though. Should put some time to that.

Do you use it mostly as "labeled breaks" or to throw the values out of closures?

  • I haven't used it much at all, to be clear. I just found it surprisingly fun the last few times I played with it.

    The specific thing it made a lot easier was implementing algorithms the way that Knuth writes them down. Which is very much a set of steps with specific calls on what step to go to next.

    I think the reason I found it fun to play with was that I found that style of laying out what needed to be done was easier to work with than the standard breakdown that making everything a function or an object seems to require. For me, it was a lot easier.

    Edit: I have one of the times I used this here: https://taeric.github.io/many_sums.html I did not put any effort into cleaning up that code, though. So it can probably work as an argument in either direction. :D