Hacker News new | past | comments | ask | show | jobs | submit login
The History, Controversy, and Evolution of the Goto Statement [pdf] (sonoma.edu)
59 points by dlivingston on Nov 19, 2018 | hide | past | favorite | 76 comments

This is an excellent article that sets context for, and summarizes, the “goto debate”.

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.)

Wait, what? From my memory, the Dijkstra comment was about arbitrary jumps from Goto statements. Like, where you could jump into another function with a completely different stack state. I saved this reddit comment that said it perfectly:

>>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!


Dijkstra does not really distinguish between types of GOTO. He is objecting to the whole concept of GOTO, not just particular "bad" uses of GOTO.

> 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.

You can understand what Dijkstra's comment was about, by reading both the original source (https://homepages.cwi.nl/~storm/teaching/reader/Dijkstra68.p...) and the responses to it (the most comprehensive being Knuth's https://homepages.cwi.nl/~storm/teaching/reader/Knuth74.pdf).

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)”).

> 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.

The rest of my comment (everything except the last sentence you quoted) was precisely about showing that the debate was about, and did cover, break, continue, early return, 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.)

Goto is good to have. With enough other flow controls you will not use goto so much, although sometimes it is best way to do, by goto. Just because it is uncommon that it will be helpful does not mean that it should be never used; sometimes it is much more clearly and cleanly than the other way which might be messy in some case. Unfortunately, JavaScript does not have a goto command (although I wrote a way in which a preprocessor could convert programs using goto, to the one without, with the restriction that you cannot jump into a block) (what else I don't like about JavaScript is automatic semicolon insertion, although that is a different problem). Of course there is the consideration of implementation, since you will have to keep track of the labels, which may be forward or backward, rather than only the nesting level stack like implementing break/continue and if/else.

Any time I have felt the need for something like goto, I have found that need served well by a function with multiple return statements. You're right that it can't simulate jumping into a block though, but that seems like a feature to me. Also, refactoring the necessary portion of code into a function is usually an improvement in its own right.

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).

The funny thing is, it became a cargo cult in many ways. I once worked with a guy who persistently wrote things like these in C++:

   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.

To be fair, break is not goto, since it adheres to the block structure of the code, while goto can jump to arbitrary locations.

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.

Jumps to arbitrary instruction addresses (goto) are unavoidable and the dispute is about how to structure such jumps by effectively replacing goto by some other mechanism:

- 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

I think we should distinguish between gotos at language level and jumps in the underlying machine code. Djikstra was concerned about correctness at the language level. The whole point is that a structured high level language can provide correctness guarantees which is not present in the underlying machine code, even though all control structures compiles down to jumps.

Gotos are not unavoidable in high level languages.

> I think we should distinguish between gotos at language level and jumps in the underlying machine code.

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).

You lost me here. Is this assembler? The question is if and when to use goto in higer-level code, not how to use jumps in machine code, which is a completely different question. What keyword you use for the instruction does not matter.

I've seen and use that. It's perfectly readable once you understand it's not a loop. It's handy on c, where you can't try...throw (or don't want to in c++) where you want to do a few things which you want to treat in a transaction-like manner. You would return success from the last line in the do loop and handle errors outside the loop. It's not just about mindlessly avoiding goto; it's about writing clean structured code.

The problem is trying to understand that it’s not a loop. The first line of the block says “this is a loop”. The last line of the block says “this isnt’t a loop.”

The code is, if not outright lying, at least misdirecting.

You could always add a comment "this is not a loop" if you find it puzzling that the language allows you to loop zero times.

This is wrong in so many ways. First, I know the language. I’ve been doing this for almost 50 years. This isn’t a question of what the language allows but what the coder is trying to do.

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.

In what sense is this any more clean or structured than a goto? It is literally a goto to an anonymous label, nothing else.

It's more clean in the way breaking or continuing from for loops and switch statements are. Or don't you approve of those either?

No, it's not more clean in the way those are. If you already have a loop, use break or continue. If you don't, it isn't "more clean" to introduce a pseudo-loop just so you can use break.

I'll grant that it would be handy to have a syntactic feature that would be equivalent to "break", but would apply to the current enclosing block. Basically like "return", but block-scoped - which is basically what this is, except for the whole intent/readability issue. I also like languages that let you do labelled "break" and "continue" out of the loop, rather than forcing you to put the label after the loop - IMO labeling the loop itself is more logical.

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.

> - 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 solution to this is an Error label after returning success and a "goto Error" in case of failure. The pseudo- loop plus break is not cleaner in any sense - its only purpose is to avoid typing "goto."

In c++ however using goto is restricted depending on whether you'd jump over declaration of objects with non-trivial types or not.

You can jump past declarations if you're exiting the block - it's only a problem if you're jumping past a declaration to a point where the declared object would still be visible. But the goto equivalent of this code does not require that - it would be a jump to a label after the closing brace.

of course it has to be, what would you expect it to do otherwise?

Why not

  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:

    // Attempt to set value
    if(choice == sane) goto sane;

    // Nope. Try again.
    goto: try_sane

        printf("%s\n", "Well done.");

I don't understand the example. While(false) means that the loop will not be executed again, which is the opposite of the example with goto.

Right, exactly. I assumed the parent comment was doing a single iteration of the loop. Maybe I interpreted wrongly.

A do-loop will always execute the body before checking the condition. So yes, it always executes a single iteration, making it just a block, not really a loop. The whole point of having "do .. while" is so that you can use "break" inside to go to the point immediately after the loop.

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.

I think I get it. Writing mostly in C# these days, my instinct when facing nested ifs is to break things down into functions, as you can then return from a function call in order to break out of that code block. Admittedly the problem then becomes naming the functions and potentially having to pass a lot of local state around. I guess this is easier to swallow in a class-based language like C# where you can simply wrap the variables into an object or make them fields of the method's class.

> There are very few languages today that don't have `break` and `continue`, and in them (Lua for instance) `goto` is still used.

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).

just a nitpick: it doesn't mention setjmp/longjmp in C - that is even less restricted and allows you to jump between functions. Well it is used to do something like exception handling in C; but don't do that if C and C++ got mixed as your automatic objects in C++ will not get destructed.

My father wrote a BBC Basic interpreter for MS-DOS and lectured at Coventry University for a while. I remember discussing the Dijksra article with him. He agreed that goto should generally be avoided, but said he’d used them for very deep loops where a for loop would have run out of memory. That basic interpreter was written in assembler, so he was very used to managing program structure manually with no high level constructs. On early computers, and even today, sometimes there are constraints other than clarity of the code that need to be considered. Having said that, I've not used a goto statement for at least 30 years.

I kinda understand the feeling your father had. I'm currently tutoring a friend and while I was reviewing a bit of code from the assignment they had written, a simple goto would halved a lot of the complexity involved. Instead I recommended a complicated flag based system that was probably unnecessary. Although goto is harmful, perhaps it should be taught again as an advanced technique once more?

It's a fundamental part of assembly, so I do think it's useful to be aware of it and the debate about it's use.

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.

From "Goto considered harmful": The exercice to translate an arbitrary flow diagram more or less mechanically into a jumpless one, however is not recommended.

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.

> Instead I recommended a complicated flag based system that was probably unnecessary.

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.

Sounds like a state machine implementation might work. Hard to say in the abstract.

Looking at this from an Assembler point of view, isn't everything really just a GOTO?

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....

"GOTO considered harmful" doesn't mean you should program in assembler without JMP (and without CALL, for that matter, since the ability to CALL into the middle of a function is equally problematic).

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.

> 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.

You can be sure that Dijkstra understood that control structures are implemented in terms of jumps in the underlying machine language. He probably also assumed the reader would know that.

Too many people took the wrong lesson from Dijkstra: that typing out "goto" in your code was wrong! The point is that structured programming helps you maintain invariants in your code, and reason from the syntax alone without having to know the entire dynamic history of the code's execution.

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.

Original author here. This was a handout for a computer science colloquium talk that I gave many years ago.

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!

Thanks for a great article/talk!

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.

In John Reynolds "The Discovery of Continuations" paper he cites an interesting observation by Doug McIlroy:

"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/...

Funny this should show up, since I am just in the planning stage of implementing common lisps "tagbody" in scheme. It is mentioned in the article, but not really explained. It is a neat little localised goto that is explicit. You can only jump withinthe tagbody.

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 :)

Original author here. Funny you should mention that. I once made an attempt to do the same. You can find it at http://web.sonoma.edu/users/l/luvisi/scheme/prog.scm

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!

The statement that jumping to an error handler evolved into modern exceptions (2.4) is a bit misleading. Exceptions as we know them today have three properties.

(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.

As I recall, my goal was merely to point out that unstructured jumps are still used for error handling. I was in no way attempting to claim that the two features are the same.

I'm sorry for any confusion.

No need to apologize. I didn't mean to suggest any failure of intent or diligence. The problem is only that people who have grown up in a culture of both "goto is evil" and "exceptions are free" has a poor lens through which to view the comparison. It's just one of those things that might need to be clarified in a later version as time marches on around us. In general, I found the article very well written and helpful.

An observation just for fun: if you really want to, you can solve the "average of integers, terminated by 99999" problem without needing any of { goto, break, duplicated code }. Here's one way:

  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.)

I was once in a long online debate about whether "software engineering" was about machines and math, or the human mind ("wetware"). I leaned the "wetware" direction. As a "test", I asked the machine/math side to "objectively prove" that goto's are inferior to blocks (under most circumstances). (To be fair, the machine/math side was not uniform in their view; I'm oversimplifying for brevity.)

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.

I think old style basic is very easy to learn because of goto, you can do a lot of stuff just with goto and if statements. I guess this 'goto is harmful' mantra made it much more difficult to get started with programming as you are now forced to learn more syntax before it is possible to express anything meaningful. The lack of goto has created something of an artificial barrier.

> you can do a lot of stuff just with goto and if statements

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).

I was talking about educational programming: you still have to teach variables, expressions, assignment, a bunch of library functions, etc. etc. The job of teaching is not being made easier if you also have to add a lot of non trivial syntax on top of that.

GNU C's cleanup attribute and nesting of procedures is better than GOTO in most of cases.

For a relatively recent problem caused by use of goto: https://en.m.wikipedia.org/?title=Goto_fail

That is not specific to do with a goto command itself; even if you wrote "break" instead of "goto fail", still the second one is unconditional. But either way, can easily seen that such program is not sense, so, easily to be noticed.

"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.

Break is technically a goto, albeit a nicer, more well behaved version thereof.

'if' is also technically a goto then.

Earlier this year I spent some time implementing a BASIC (actually VBA) interpreter and to support error handling (e.g. ON ERROR GOTO foobar) I ended up translating all of the flow control statements into conditional GOTOs.

The problem is not caused by the use of goto though. It could have been any other statement and it would still be a bug. The problem is lack of braces or the misleading indent. If anything this example is a case for Python, not a case against goto.

>If anything this example is a case for Python, not a case against goto.

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 intended.

That ought to be "indented".

Yeah, that mistake was inindented

Sometimes (like now) I wish I could give a +10 or +100 on HN! Thank you :-)

This example is a case for languages where the bodies of conditionals, loops etc are required to have a terminator. Python is technically one of them, but probably not the best one, because terminator is the DEDENT pseudo-token, and because it's just whitespace, it can be easy to miss.

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 ... { ... }.

Agree, and I am paranoid enough to always use braces for single statement bodies. I also think this is recognized as a flaw in C-style syntax, and newer languages like Rust mandate the braces.

I do think Python has the simplest and most logical solution though.

The editor should have indented that line correctly.

Applications are open for YC Summer 2021

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact