5 ms·
Yes. Because giving it a different name addresses the problem with goto. The problem with goto is that it is opaque. It has no structure, it has no constraints
by lmkg 9y ago
Yes. Because giving it a different name addresses the problem with goto.
The problem with goto is that it is opaque. It has no structure, it has no constraints, and it communicates no intent. It says "execute the code behind this label," and gives no clue what the code is or what it does.
But if you give goto a different name that reflects how you're using it, that solves the problem! If your goto is named "break" and it occurs inside a loop, that tells you what the goto is trying to achieve. It tells you at a glance what code you expect to find when you jump, and its relationship to the other code in the program. In short, it communicates intent.
The same is true when the name is "continue" or "throw": you know where in the program that goto leads and what sort of code you expect to find when you go there. You don't need to read it to find out. And if you do read it, you have a better idea of whether it's right or not.
(Also note that criticism of goto applies a lot more to historical gotos, whose argument was an arbitrary memory address. Modern gotos aren't nearly as bad because they generally take a label rather than a number, and because their jump targets are often restricted to the immediate lexical environment.)
- chriswarbo 9y ago> The same is true when the name is "continue" or "throw": you know where in the program that goto leads > Modern gotos aren't nearly as bad because they generally take a label rather than a number, and because their jump targets are often restricted to the immediate lexical environment. That doesn't actually apply for your examples of "continue" or "throw" though. "continue" has no label, numeric or symbolic; its target is implicit from the lexical scope. "throw" has a sort of label (the type/class of the given value/exception), but its target is determined by the dynamic environment. Given a function like: function foo() { throw Bar; } There is nothing in the lexical environment (i.e. the blocks within which it is written) that tells us where this will jump to. That information depends on what the stack looks like at the call site, which may be written by someone else, on the other side of the world, 20 years from now.
- lmkg 9y agoI apologize if I wasn't clear, I didn't mean to say that continue/throw are similar to modern gotos. Renamed gotos such as continue, break, and throw address the primary drawbacks of raw gotos. Unrelatedly, the gotos that one is likely to encounter in a modern-ish language like C or Common Lisp don't have all of those drawbacks anyways. My purpose was not to draw a comparison, merely to add some additional context for Dijkstra's original rants that may explain why the hard-line stance he took at the time makes a little less sense today.
- deleted 9y ago[deleted]
- gpderetta 9y agoTechnically the same thing is true for 'return'.
- chriswarbo 9y agoThat's true, although 'return' will only pop one frame off the stack whilst 'throw' will keep going until it finds a handler. Hence 'return' has a more predictable/consistent behaviour, and less context-dependence.
- gpderetta 9y ago> although 'return' will only pop one frame off the stack Unless it was a tail call... Really, throw and return are quite similar, both invoke an externally provided continuation and abandon the current one.
- gpderetta 9y agoedit: but yes, of course I'm being pedantic.
- chriswarbo 9y agoHeh, I actually wrote something about continuations, then decided against it. I was going to say: In a pure CPS setting, the caller's "only" responsibility is to give the right arguments (in scare quotes because getting a continuation right is usually trickier than the "plain" values usually given to functions). In languages with functions, the caller is also responsible for handling/ignoring any return value and carrying out any subsequent computations, if the procedure returns. In languages with exceptions, the caller is also responsible for handling/passing-on any exceptions. Out of these three sorts of language, my current preference is for the second. It's probably fair to say that most procedures in a codebase will return a value (i.e. they're not continuations) and will not throw an exception (they're exceptional, after all). Hence it seems empirically desirable to allow `return`: it alters the semantics from a pure CPS scenario to one where we must always be vigilant that a procedure may return; but since most procedures (in such languages) return, that's not an edge case (in fact it's continuations, procedures without `return`, that are rare!); hence the benefits seem worth the cost. When `throw` is introduced, we again must be vigilant when we're calling procedures. However, since most procedures don't `throw`, the benefit of having this ability is less than that of `return`. Also, since there may be many occurrences of `throw`, with different exception types, the cost of looking out for and handling these is greater than for `return`. Hence my (current) opinion that `return` is very much a "good GOTO", whilst `throw` is a "bad GOTO" :) Then we get `call-cc`... ;)
- deleted 9y ago[deleted]