The History, Controversy, and Evolution of the Goto Statement [pdf]
web.sonoma.edu
web.sonoma.edu
I know modern programmers who have simply absorbed the received wisdom that “goto is terrible” and wonder why there even is/was a debate; this short and concise article should show what goto was used for.
At the time Dijkstra wrote (the letter that the editor titled) "Go To Statement Considered Harmful", his proposal would have meant programming in a C-language (of course I know C didn't exist then...) without any `break` or `continue` in loops, and without `return` except at the end of a function. There are very few languages today that don't have `break` and `continue`, and in them (Lua for instance) `goto` is still used.
As the article mentions, it takes much from Knuth's wonderful summarization paper “Structured Programming with Go To Statements”, and if you find this interesting you should definitely read it! https://pic.plover.com/knuth-GOTO.pdf (Also reprinted in his book/collection titled Literate Programming.)
It's funny that the GP comment mentioned this but the other comments have all focused on break/continue. I suppose the backward jumps you mention are usually only served by something like break/continue. But I find that when beginners are starting to learn to program, they very often ask for something that sounds a lot like a goto, but their need is also always satisfied by a function with multiple returns (which usually involves explaining what a function is).
do {
...
if (...) break;
...
} while (false);
It took me a while to realize what this was about. But, basically, he invented a way to goto (so long as it was forward) without actually spelling it out. When I asked him, he gave the usual creed about how "goto is bad", and wouldn't be convinced that break is goto, especially given how he used it.Needless to say, this kind of code is actually less readable than if it were written with a goto in it, because it looks like a loop, but actually isn't one. With goto, at least it's very clear what it's for, even when there was a better way to do the same thing.
But we should be cognizant that those features are still extremely close to a naked goto in terms of the ability to reason about the code - it's just a sprinkling of syntactic sugar on top. So if goto is unequivocally bad, then e.g. early returns are also bad for all the same reasons. Conversely, if those features are fine when used in moderation or in well-established patterns (like "if (error) return") - which I believe is the case - then so is a naked goto. It's the cognitive dissonance of people who use them heavily while insisting that goto is absolutely evil, no exceptions, that irks me.
Sounds like we are in agreement then.
The code is, if not outright lying, at least misdirecting.
Second, experience has taught me that code and comments often don’t agree. Too, if you have to comment that a loop really isn’t a loop then this is an indication that something is wrong.
Third, it isn’t a loop: while(false) never loops. That’s the point. Having to use a forward reference to understand the beginning of something is bad practice.
Fourth, this construct is almost always a sloppy way to prevent if-else creep. The code is invariably improved by a) not lying about the loop and b) refactoring.
...
if (...) {
...
}
?Are there more complex examples where this becomes if-else hell?
do {
// Attempt to set a value here, but maybe not
// succeed
// Like asking a user for a specific set of values
...
if(choice == sane) break;
// User did bad. Try again.
} while(false);
Sometimes, you need a way to repeatedly call the same unique piece of code until it becomes something valid.However, goto serves it, and does it in a much clearer way, in my opinion:
try_sane:
// Attempt to set value
...
if(choice == sane) goto sane;
// Nope. Try again.
goto: try_sane
sane:
printf("%s\n", "Well done.");Yes, with a single check, you might as well just do "if" instead. The actual code involved numerous such checks. Think of code that repeatedly calls various APIs that return error codes; if any call returns a non-success code, the code must immediately exit this block. You can just keep writing nested ifs, but then you start getting crazy nesting levels after a few calls, which makes code pretty hard to read. So, most people prefer to just jump out instead, resulting in a flat code on the success path, with all errors handled with jumps.
In C or C-like C++ (sans exceptions), this idiom is extremely common, and usually it's done with "if .. goto error" or something similar, often wrapped in a macro.
The code in question should of course be written with an if. A goto would not really be an improvement, both goto and break are really weird way to express something which seems to be simple conditional.
I agree goto's a fine if they are the simplest and most readable solution. But this does not seem to be such a case.
- Introduce scopes and program structure instead of explicit labels (instruction addresses) and using control flow operators which rely on this structure
- Procedures and procedure calls
- Overriding and virtual functions where the system knows how to find the next instruction to jump to
- Concurrency and multi-threading where resume operation means essentially jumping to some (dynamic, not static) instruction address.
- Asynchronous and even-driven programming
Gotos are not unavoidable in high level languages.
Is there any non-syntactic difference between these two lines?
JMP Ox1234
GOTO label_1234
I think they are identical and the question is only where target labels can reside, how they are represented, and when the jumps are triggered (operation).Oberon is one of those few "pure" structured languages where a statement sequence is never interrupted. The latest version of Oberon doesn't even have a return statement (which is also also a form of goto unless defined as the last statement of a subroutine).
>>C goto is not the type of goto that Dijkstra was complaining about. It's a tamed sort of goto. The sort of goto that Dijkstra was complaining about would allow you to do things, in modern terminology, like leap from one function directly into the middle of another function, in the middle of two loops, an if clause, and three additional nested exception contexts the goto-using function doesn't have.
>>That is to say, we've built so many abstractions on top of structured programming that the modern mind can barely even comprehend the sort of goto that Dijkstra had in mind. He won his point so thoroughly that structured programming is simply the water that modern-day programmer fish swim through, taking for granted. The only gotos that are permitted are ones that honor all of the structured programming criteria. And then people get all excited saying that Dijkstra was wrong, because look at how safe these goto statements are! They can't possibly catch on fire, what with all this water we're swimming in!
https://www.reddit.com/r/programming/comments/8lm8p7/30_year...
See for instance page 277 in Knuth's paper:
> Comparison of Features
> Of course, event indicators are not the only decent alternatives to go to statements that have been proposed. Many authors have suggested language features which provide roughly equivalent facilities, but which are expressed in terms of exit, jump-out, break or leave statements.
It goes on to discuss more alternatives (at least one of which I find in fact better than the "break" and "continue" we happen to have got with C). It also mentions:
> what I believe is really the most common situation in which go to statements are needed by an ALGOL or PL/I programmer, namely [a loop which is performed "n and a half times"]
This is also what the linked article discusses in section 4.1, the first one under "Common uses" (http://web.sonoma.edu/users/l/luvisi/goto/goto.pdf#page=5).
If you restrict Dijkstra's comment to only apply to arbitrary jumps such as those into another function, then it does it a great disservice: there's no force in the argument anymore (it's a banal claim that nearly everyone always agreed with... in fact some of the languages in which goto was being debated at the time didn't even support such non-local gotos), and there would have been no controversy. It's worthwhile to consider thoughtfully the stronger claim that Dijkstra actually made (“the go to statement should be abolished from all 'higher level' programming languages (i.e., everything except, perhaps, plain machine code)”).
But that claim sidesteps whether break, continue, early return, and try/except count as being the same, in spirit, as the literal 'go to'.
"Don't literally use goto" is just as banal as you're claiming "don't goto the inside of another function" is. All the proffered examples of "good goto" are just explicit versions of break etc.
And yes, the examples given at the time of places were goto was useful are what would now be written with "break", "continue", etc., precisely because those were at risk of getting worse under a blanket "let's abolish goto" -- from which we can draw the conclusion that the debate at the time was indeed about them, and not merely about things like jumps into other functions.
If we're debating what Dijkstra's comment was about at the time, and what the ensuing debate was about (a historical question), IMO we should do that by reading/quoting the primary sources, and not each other's comments. :-) I think we actually agree BTW.
--------------------
Let me try rewording.
At the time Dijkstra wrote his letter, "goto" was being used in many ways. The controversy ignited by Dijkstra's letter was whether all those uses of goto should be abolished. (That's why there was any debate at all; if Dijkstra had only said "let's remove jumps inside another function" there wouldn't be as much disagreement.)
During this debate, people identified cases where "goto" was indeed necessary/useful, and those people were right, as we can see from the fact that some of those control flow mechanisms were introduced into languages under names like "break", to cover those examples.
And yes, more recent languages like C have relatively tame gotos as well, as you said in the original comment. Both sides can be said to have won some points thoroughly: Dijkstra's (and others') warning against the "disastrous effects" of the goto statement led to languages locking down the kinds of "goto" they provide, and the other side's examples of useful "gotos" (in the languages of the time) led to languages almost universally incorporating "break", "continue", early returns, error-handling mechanisms, etc., to widen the scope of control flow that was possible without "goto". Try programming in some of the common languages circa 1968 (which didn't have these) without using "goto", and you'll see why there was a debate. :-)
(Restricting Dijkstra's argument to only that part of it that eventually won thoroughly would make it appear that people were idiots for arguing against it, which is both historically inaccurate and ignores the influence from those arguments over current languages. And of course, restricting his argument to only the part that "lost" would make it seem like he was wrong and ignore his immense contribution, but I don't think I did that, did I? If I did, my apologies: my comment is relative to the present time, and what I think many people misunderstand about the debate historically.)
> C goto is not the type of goto that Dijkstra was complaining about.
Well C did not exist when Djikstra wrote his article, so I guess this is technically correct. But from his argument it is clear his criticism pertains to any kind of GOTO, not just GOTO's which jumps across function boundaries.
While I personally agree that C GOTO is less bad than unconstrained GOTO, that is not a distinction or argument Djikstra makes.
I learned to program on MS Basic on a CP/M machine and made heavy use of GOTO. When I started learning structured programming I had a hard time understanding how program flow could work without GOTO, so GOTO was harmful to some extent because it ingrained certain expectations and habits that were palpably bad and an obstacle to learning, but probably because I learned to sue GOTO in an undisciplined way.
One of the nice things about programing in Basic back in the day was you had complete access to the hardware. You could POKE a memory address and see a dot or character appear on the screen. Basic was easy to use, but closer to assembly than modern languages in some ways even though it was interpreted. BBC Basic was really nice because it had high level constructs and low level system access.
That's the trigger for me: after more than one flag and the complex states involved, I may refactor the code using goto because it can actually be a cleaner solution. Programmers need to be less dogmatic about this case.
Djikstras problem with GOTO was that the source code structure does not correspond to the execution path. GOTO turns the execution into a state machine rather than a nice tree. Avoiding GOTO by implementing a state machine with flags is just creating the same problem one level up.
The problem with GOTO is not the keyword itself, it is what it does to the code.
Instruction Register + 1 = GOTO next memory location
If A then B = Branch if equal = GOTO ADDR B
call function = put currebt address in stack and GOTO this address, then GOTO address on stack.
It seems to me that it is just GOTO all the way down. In fact there really is nothing else but Memory and GOTO.
Sort of ironic that the very foundations of machine computing is not a very good idea....
I'm not sure anyone (knowledgable) has ever said that they are a fundamentally bad idea. The argument has always been, effectively, that they are prone to producing code that is hard to reason about. Specifically, they are prone to producing code that requires non-local reasoning. That's something that people tend to find hard.
You can find variations of the same issue with such things as the actor model of threading or structured concurrency. They're all attempts to localise activity, at the expense of some power. And similarly, they almost always get translated to a more powerful, non-local construct further down the stack.
All flow control is really just GOTO in the same sense that all programming languages are just assembler. By beginning of the century, most programmers had realized that this is not the case.
This lesson applies equally well to Angular's "watches" for example, or the heavily event-driven style seen in some Backbone applications, but these weren't spelled "goto" so people didn't notice.
It's fun to see this show up on Hacker News, and thank you all for not holding it to the same standard as a proper academic paper!
Many people misunderstand the goto debate and I'd like to point them to the great Knuth article for a summary but I can't; it's far too long and contains a lot of implicit context of its time. Now I have something much better to, well, ask people to go to.
"So while van Wijngaarden said that goto's were unnecessary ... , Dijkstra stretched the point to say that goto's were inconvenient. The latter lesson stuck."
Essentially Van Wijngaarden was talking about a continuation passing style, where the remainder of a program is represented as a function, so at the end of a function you always call another function and no function ever returns! Read the paper: https://homepages.inf.ed.ac.uk/wadler/papers/papers-we-love/...
Why is this useful? Well, goto is a nice little primitive for some things. Tagbody is used in CL to implement the iter (and maybe loop) macro. Because sometimes all you want is unconditional jumping around :)
One fun thing about this implementation is that labels can be passed to other functions, or returned from functions, and even used to jump back into functions that have already exited, since they are based on continuations.
Good luck!
(1) The target is not necessarily known at compile time.
(2) The catcher is likely not the same function as the thrower, with several other functions in between.
(3) The stack is unwound as part of the throw/catch, including things like destructors.
Even the uber-powerful "goto" that Dijkstra railed against didn't have these properties, let alone the tamer "goto" of modern languages like C. Personally I think (1) and (2) are reasons why exceptions are far worse than gotos, but if you're going to have them then you'd best have (3) as well. What I always find strange is people who look down their noses at code with gotos carefully used for error cleanup, but have no qualms about flinging exceptions around in their own code. I guess those who do not learn the lessons of history, etc.
I'm sorry for any confusion.
int sum = -99999;
int count = -1;
int last = 0;
do {
scanf("%d", &last); // scanf is kinda evil but is what article uses
sum += last;
count += 1;
} while (last != 99999);
Or you can initialize sum and count to 0 and correct them after the loop instead of pre-correcting before as the code above does. This results in code that is a little longer, very marginally slower, and somewhat easier to read.(There is still a little bit of duplication there: the magic number 99999 appears twice. For this reason I would actually use break, unless required not to like some of the students in the experiment.)
It was a fascinating debate, but ultimately degenerated into a dispute over the definition of "go to" and "block", because the boundary is actually quite fuzzy being that hybrid structures can be created. One would probably have to settle on specific existing languages first in order to compare.
You can do all stuff with goto and if, but then again, why should you? Initially it sounds simple, but as the complexity grows it becomes unmaintainable (especially when goto is unstructured, or non-local, such as the "On Error GoTo ..." statement). For some time, I had to fix hundred-line legacy VBA functions and it was hard (i.e. forget trying to do it without a debbugger).
"There are several good coding practices that could have prevented this fault from occurring, such as code reviews, the proper use of indentation or curly braces, and test coverage analysis." Yes, it is true, whether you use goto or not, whether indentation-sensitive syntax is used or not, etc.
You'd be surprised how many times I've got the indentation wrong in Python.
If a body gets > 20 lines or so, it's easy to miss one statement that ought to be intended.
That ought to be "indented".
The best examples are languages like Lua and Ruby, where you have to write it as if .. then .. end or if .. then .. else .. end. Alternatively, in the curly brace family, it's the ones that make braces mandatory in this situation, like Go - if ... { ... }.
I do think Python has the simplest and most logical solution though.