
Loop Exits and Structured Programming: Reopening the Debate (1995) [pdf] - tjalfi
http://www-cs.stanford.edu/people/eroberts/papers/SIGCSE-1995/LoopExits.pdf
======
glangdale
Goto gets a bad rap, and I have seen many programs where an explicit goto, to
a label that is descriptive, is _way_ clearer (and less brittle under
reorganization) than a 'structured' break or continue - or some spaghetti of
booleans (while (!done) ... ).

It's clearly very easy to write bad code in many ways, but (very) judicious
use of goto is occasionally far clearer than assuming goto is always bad and
building something complex out of bools, break and continue. It seems to be a
test of common sense: if goto is clearer than the alternative, use goto.

~~~
dbaupp
Your specific example sounds like the kind of thing addressed by more powerful
structure: labelled break, as appears in Java, JS, Rust and Swift (among
others, I'm sure).

Of course, thinking about labelled break is just dreaming when
required/choosing to work in a language without it.

~~~
sedachv
Labelled break is strictly less powerful than gotos, and IMO its
implementation in Java is far inferior to the _block_ Common Lisp special form
([https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node85.html](https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node85.html))
because s-expressions make the lexical scoping unambiguously visible.

IMO there is no good substitute for gotos when writing transpilers (and
certain interpreters) and low-level concurrency code. The Common Lisp _prog_
macro is the best way that I have seen to use gotos
([https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node91.html](https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node91.html)).

~~~
dbaupp
Of course it is strictly less powerful than gotos. That's actually the point
of the more structured constructs: they are more restrictive and so can be
reasoned about more easily. Note that I'm specifically piking up on labelled
break/continue as a substitute for nested loops like the following

    
    
      while ... {
        bool shouldBreakOuter = false;
        while ... { ... shouldBreakOuter = true; break; }
        if (shouldBreakOuter) break;
      }
    

since that what the original comments description sounded like. Being a rather
general control flow mechanism, there are of course other uses of goto which
this doesn't cover.

That said, I'm not sure I understand the comparison: the lexical scoping
doesn't seem to be ambiguous in Java. Could you expand on what you mean?

~~~
sedachv
If you visualize nested scopes as indent levels, the _labelXYZ:_ sits in an
indent level above where it is visible. Not only that, but _labelXYZ:_ does
not come before the statement that it transfers control to, but two statements
before. If all that was not bad enough, most people format the labeled
statement to sit at the same indentation level. The combination of these three
things is pretty much the worst possible when it comes to reading code.
Compare this to _block_ / _tagbody_ / _prog_ and their indentation conventions
in Common Lisp.

------
ajarmst
When I teach this stuff, I try to distinguish between (1)
Returning/Exiting/Excepting because of a legitimate error condition (2)
Returning/Breaking early because there's either no work to do or something
necessary to do the work is absent/unavailable (the latter often turns into a
case of (1) (3) Returning/Breaking/Continuing early because I've discovered
the work is done for either all iterations or this particular iteration, and
the remainder of the function/method/loop is pointless cycling. (Often added
during optimization, sometimes added too early). (4)
Returning/breaking/GOTOing because I've gotten confused and want to go to a
place in the code that makes more sense to me. This is the only that's
actually a problem and should be railed against. Unfortunately, it's way more
common than it really should be in beginners' programs. I think GOTO got an
early bad rap---try writing non-trivial machine code or assembler without
liberal use of goto, and many very useful techniques in debugging are
basically informed application of goto---because of what I call the "pull the
ejection handle" approach to getting into trouble trying to code an algorithm
you dont really understand.

------
woliveirajr
I love all those "goto" disguised as any kind of flow controls. Because goto
is devil, but give it another name and another smell and, voilà!, it becomes
beautiful.

~~~
lmkg
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
> 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
I 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.

------
cabaalis
1\. Does the code do what it's supposed to do, without crashing or causing
unwanted side-effects?

2\. Is the code relatively readable by someone unfamiliar with the problem?

If 1 && 2 then stop tearing your robes like Pharisees and go solve another
problem.

------
graycat
Ah, the OP probably has better ideas, but I know what I like! E.g.:

    
    
         Do Forever
           If A Then Leave
           If B Then Call X
           If C Then Iterate
           Call Y
           Leave  
        End
    

Ah, that style of loop writing may cause some purists to scream bloody murder!

~~~
mcguire
I used to know a guy who used a loop structure something like that. For every
loop. One of threw weirdest things I have ever seen.

Some people seem determined to cause facial tics in everyone they meet.

~~~
graycat
> Some people seem determined to cause facial tics in everyone they meet.

Maybe so, but not me.

So, what is the justification for the Do Forever loop above?

First, I illustrate that we can want to exit the loop for (A) whatever
conditions are discovered in the loop and (B) at any point in the loop and not
just the beginning or end which is the usual situation, e.g., for

    
    
         Do i = 1, 10
    

or

    
    
         Do While X
    

If I write

    
    
         Do I = 1, n
    

does the loop logic deciding when to exit ever form n + 1? If n is the largest
integer, then maybe we'd like to know. With Do Forever we may have more
control, enough more to avoid forming n + 1 unless we really want to.

Second, when I start writing a loop, I may not have yet worked out all the
details or how to get what I do want just from some

    
    
         Do While X
    
    

Etc. So, I just start the loop with Do Forever and work out the exit
conditions as I keep typing.

Third, with Do Forever can often get the results of using a GOTO without
actually doing so. So, if the target of the GOTO would be just after the loop
End, then can get there within the loop with just a Leave.

Of course, when write such a loop with Do Forever, should write some comments
that explain (A) what the purpose of the loop is and (B) how the code in the
loop does accomplish that purpose. That is, should not force someone reading
the code to guess at (A) and confirm (B).

For proofs of correctness based on the usual mathematical induction, I'm not
convinced that loops with Do Forever are fundamentally more difficult.

> Some people seem determined to cause facial tics in everyone they meet.

Maybe so, but not me.

------
fmatthew5876
Its really not that hard. Instead of blindly following rules someone else
wrote, just read the code. Is it easy to follow? How many details do you have
to juggle in your head? How many indented scopes are there? How much state is
there? Can you mostly read it from top to bottom, or do you constantly have to
jump around the file? If someone unfamiliar with the code (maybe you in 3
months) needs to make a change, how easy would it be for them to overlook some
important detail and mess it up?

Best practices usually apply in general, but the smart person who coined them
cannot know all possible situations and the parrots who blindly repeat them
don't know what they're talking about. Think for yourself, know your tools,
and use them effectively to make your code as simple as possible.

------
eru
Just use functions calls already. Modern languages and compilers /
interpreters have gotten good enough (at least the ones that care), that
there's no need to bless a small set of control structures by being special-
purpose built-in.

~~~
mcguire
Lambda, the ultimate goto!

~~~
eru
Yes, that's the paper I was referring. They also explain why TCO is (or should
be) the natural style to write your compiler for.

------
awinter-py
This is relevant. There are lots of real-life errors that come out of these
errors and the worse mistakes people make trying to work around control flow
(goto fail?).

I desperately want a 'break break' command. Not a label break, which is
painful to work with, but an instruction to break the current loop and the
outer loop.

~~~
danthemanvsqz
Wrap the double loop in a function/method and return from the inner loop.

~~~
Animats
That works well except when you're in a language where you can't nest
functions and need data from the outer scope. C is like that.

Rust really likes to bail out of functions when something goes wrong. That's
what "try!()" does.

Allowing multiple exits from a loop is no longer controversial. Multiple
entries into a loop are still considered undesirable.

From a proof of correctness standpoint, the topology requirement is that there
be some single place within the loop through which control must pass. That's
where the proof inductive step takes place, and where you prove loop
termination by showing that some measure is decreasing. Restrictions stricter
than that are more stylistic than formal.

~~~
graycat
Ah, you seem to be talking about having the source code of function X inside
the source code of function Y and, then, the names known in function Y are
also known in function X unless declared again within function X. I LIKE that!
That's what PL/I has.

Once I got a phone interview from Google. An early question was,

"What is your favorite programming language?"

Sure, right away, PL/I! Opps, (from a movie) "way wrong answer!". Apparently
the only right answer was C++. Gee, I didn't want to lie. Besides, to me
saying C++ instead of PL/I should cause me to lose a full letter grade!

So, PL/I has _descendancy_ , static and dynamic. The static version is from
the nesting in the static source code. The dyanmic version is from functions,
subroutines, etc. that have been called (are active) but have yet to return.

Then with such descendancy and entry variables, can get some interesting
situations, _design patterns_!

I did that once and avoided a total mess in the IBM AI language KnowledgeTool!

------
GrumpyNl
Software, what its all about: assignments if and while loops

~~~
tuukkah
Only imperative programming though. (Functional programming is the opposite.)

~~~
eru
And even in imperative programming, you don't need the loops. At least not as
a built-in.

~~~
mort96
You don't need ifs either, just conditional goto.

~~~
eru
You don't need ifs nor conditional jumps. Functional calls are truly enough. I
can show you how it's done, if you want to see it.

Thus you can implement 'if' as a user defined function in eg Haskell.

(But I'd advise against the totally if-less style. Pattern matching is just
too convenient. (And ifs are just a special case of pattern matching on
booleans.))

Pure lambda calculus doesn't I have ifs. If you want to go truly crazy, check
out SKI calculus. It doesn't even have lambda abstraction.

~~~
GrumpyNl
The basics still stands, doesnt matter how you call it or do it different.

~~~
eru
Sorry, what do you mean?

If I may guess: you want to make gotos central? That only holds for compiling
to von-Neumann machines. Some other computational structures might not even
have instruction pointers to move around.

