Last updated 1999-02-23.
I haven't programmed in BASIC in years, and I never did most of this stuff. I simply wanted to explore possible idioms using `goto' that didn't show up in programs written by programmers educated in `structured' programming. Please mail me if you have seen other uses of goto.
I think that some of these might actually be OK under some circumstances, but some of them are definitely creeping horrors from the dark ages. Please don't read further unless you are an experienced programmer and have a strong stomach. (If you are not an experienced programmer, you should learn to write good code before you see this stuff.)
The traditional `structured' combinations are sequence (which doesn't require gotos), alternation, and closure. Alternation with gotos looks like this:
100 IF A = 3 THEN 130 110 REM IF A DOESN'T = 3 THEN DO STUFF HERE 120 GOTO 140 130 REM IF A = 3 THEN DO STUFF HERE 140 REM EXECUTION CONTINUES HERE
Closure -- while (a) { b; } in C -- can take two forms:
200 IF A = 0 THEN 230 210 REM LOOP BODY GOES HERE 220 GOTO 200 230 REM EXECUTION CONTINUES HERE
and the traditional form:
300 GOTO 320 310 REM LOOP BODY GOES HERE 320 IF A <> 0 THEN 310 330 REM EXECUTION CONTINUES HERE
The traditional form easily admits of a marginally-faster run-at-least-once version, which is in C as do { b; } while (a); and is not generally used by people who have not written it in the goto form:
350 REM STUFF BEFORE LOOP GOES HERE 360 REM LOOP BODY GOES HERE 370 IF A <> 0 THEN 360 380 REM EXECUTION CONTINUES HERE
goto gives you the freedom to put the various chunks wherever you want, though. This can be useful if your BASIC line-numbering is inflexible (i.e. no RENUM) or if you're writing raw machine code (without an assembler). But more often, it just happens, making the program harder to read.
You can insert extra statements in a sequence like this:
400 GOTO 10000 401 REM PROGRAM CONTINUES HERE 420 REM REST OF PROGRAM GOES HERE . . . 10000 REM CODE THAT COULD HAVE GONE ON LINE 400 GOES HERE 10010 REM BUT IT TOOK MORE THAN ONE LINE 10020 GOTO 401
After a while, the result is segments of code that look like this:
450 GOTO 760 451 GOTO 520 452 GOTO 2000 453 GOTO 2100
In a sense, this is a sort of subroutine call -- it's just that the subroutine is only called from one place, so it can use GOTO instead of RETURN to return. In itself, that's not so bad, except that the subroutine's name is a number, it is nontrivial to determine whether the GOTOs above really are `subroutine calls' or one of the other creeping horrors below, and trying to use the subroutine somewhere else requires that you modify it and possibly the call to it.
You can do similar things with alternation:
500 IF A = 3 THEN 10100 510 REM DO STUFF HERE IF A <> 3 520 REM REST OF PROGRAM GOES HERE 10100 REM DO STUFF HERE FOR A=3 10110 GOTO 520
The tricky thing about that one is that the "else" case -- where A <> 3 -- may be empty, and it's hard to tell where it ends. The more extreme case is a little better in that regard:
550 IF A = 3 THEN 10200 ELSE 10300 560 REM REST OF PROGRAM GOES HERE 10200 REM DO STUFF HERE FOR A = 3 10210 GOTO 560 10300 REM DO STUFF HERE FOR A <> 3 10310 GOTO 560
You can do all sorts of similar tricky stuff with closure:
600 IF A <> 0 THEN 10400 610 REM REST OF PROGRAM GOES HERE 10400 REM LOOP BODY GOES HERE 10410 GOTO 600
700 GOTO 10510 710 REM REST OF PROGRAM GOES HERE 10500 REM LOOP BODY GOES HERE 10510 IF A <> 0 THEN 10500
Those are two of the obvious variations.
Scheme has a nifty concept called a "tail call", wherein a call to another function as the last thing in a function can be optimized into a goto to the beginning of that other function. This means you can do infinite recursion without blowing up the stack.
You can, of course, do this yourself with gotos:
800 GOSUB 10600 810 REM REST OF PROGRAM GOES HERE 10600 REM ROUTINE FOO 10610 REM PUT FOO'S BODY HERE 10620 GOTO 10700 10700 REM ROUTINE BAR 10710 REM PUT BAR'S BODY HERE 10720 RETURN
This actually describes some of Knuth's examples of good uses of goto in his article ``Structured Programming with GO TO Statements'' -- the examples of his I remember were a recursive binary-tree search routine (made nonrecursive with GO TO) and a state-machine simulator (where the routine for each opcode ends with a GO TO NOP_INSTRUCTION statement).
Knuth's article was, I think, published before Scheme's inventors came up with a precise notion of what a ``tail call'' was.
I should note that tail-call elimination, which is a mandatory ``optimization'' in Scheme, is in general not possible in C. For compatibility and support for variadic functions, the stack area in which a called function's parameters are passed is both created and destroyed by the caller in programs written in C. This is because the caller (obviously) has to build that parameter block, and in C, there's no way to guarantee that the caller could see a prototype of the called function, and so the caller may have pushed the wrong number of parameters -- only the caller knows for sure. So the callee can't tear down its own stack frame and replace it with the stack frame of a callee.
Fortunately, in C, you can't goto another routine (essentially for the same reason), so the above goto idiom is considerably more readable in C than in BASIC.
You can, as mentioned above, return from a subroutine called from only one place by using goto. But you can also return from a subroutine called from several places by using goto -- you just have to store the place to return to in a variable:
900 LET R1=1 910 GOTO 10800 920 REM EXECUTION RETURNS HERE 1000 LET R1=2 1010 GOTO 10800 1020 REM EXECUTION RETURNS HERE 1100 LET R1=3 1110 GOTO 10800 1120 REM EXECUTION RETURNS HERE 1200 LET R1=4 1210 GOTO 10800 1220 REM EXECUTION RETURNS HERE 1230 REM REST OF PROGRAM GOES HERE 10800 REM BODY OF SUBROUTINE GOES HERE 10810 IF R1 > 2 THEN 10840 10820 IF R1 = 1 THEN 920 10830 GOTO 1020 10840 IF R1 = 3 THEN 1120 10850 GOTO 1220
This is the creeping horror that made me want to write this page in the first place.
This obviously doesn't work well for recursive subroutines. For that, you need to maintain an `R1 stack' (either for each routine, or for all recursive routines).
You can often remove a subroutine by inlining it, if you so desire. If the subroutine is called in many places, you may expand the program, but you can still inline it in all those places.
But with the techniques above, you can inline all subroutines everywhere, even recursive ones.