Go To Statement Considered Harmful: A Retrospective
david.tribble.com
david.tribble.com
Token tok;
while ( (tok = gettoken())!=END )
{
if (shift(tok))
continue;
if (reduce(tok) && shift(tok))
continue;
return ERROR;
}
return ACCEPT;
-- if continue is unavailable/taboo -- Token tok;
while ( (tok = gettoken())!=END )
{
if ( !shift(tok) && !(reduce(tok) && shift(tok)) )
return ERROR;
}
return ACCEPT; Token tok;
while ((tok = gettoken()) != END)
while (!shift(tok))
if (!reduce(tok))
return ERROR;
return ACCEPT;But does this strike you as clearer than the original? I like that the original makes it very clear that we only return ACCEPT if we reach END; that after a successful shift we continue reading; that after a successful reduce we shift again; and that we only return ERROR if a reduce fails.
I think yours works the same, but for me at least it takes longer to parse. If I were actually debugging yours, I might even resort to looking at the compiled code with objdump and writing the labels in by hand. Presuming it's correct, I bet this would result in something that looked a lot like the original!
But it's true that different expressions make different things clear. Structured programming helps most when it gets you to mirror the data's structure in the code.
In the original code, the next operation is (correctly) another reduce. In your example, you stop reducing too soon and read the next token. Furthermore, I find that the labels in the original make the logic easier to follow.
Citation needed. I know that there is a lot of programming language research that is going in this direction. It's uncommon in industry, currently, because it is hard and most programs don't need to be reliable. But, if it were easy to guarantee correct code, it's something that everyone would want. (I try to get as close as possible by using functional programming techniques. It doesn't prevent every error, but it reduces the likelyhood of "something strange" happening.)
He goes on to mention that line-by-line execution is no longer adequate. I agree, but I don't think this justifies the use of goto; it shows that we need functional programming. (Incidentally, functional programs are easier to verify than programs that make heavy use of goto.)
Anyway, I think he's gone off in the wrong direction. Goto is a very low-level construct, and it's unnecessary in high-level languages. There, we have much better control-flow abstractions, such as monads and continuations (exceptions, looping, continue/break, and function calls are special cases of continuations ).
Well, the sort of obvious reason we don't / can't formally validate most programs is that we don't have a rigorous, complete, consistent description of what the program is supposed to do.
There are times I wish I could use a goto in my code, but decide against it purely because I know it will be criticized by everyone else (or perhaps worse - used as justification for using goto in some other location)
Exactly! Spaghetti code doesn't spontaneously occur. It happens slowly but surely, year after year, one quick fix at a time. The odds against it are much better if the goto cherry is never popped.
"...purely because I know it will be criticized by everyone else..."
And also because you know better ;-)
do {
/* deeply nested code */
if (error) {
break;
}
} while (0);
/* error handling */
And no, it was not the do/while macro trick. It was advertised as structured programming.To my surprise, Dijkstra actually said the same thing. He called them "alarm exits."
http://kerneltrap.org/node/553/2131 (Scroll to bottom for pseudo code)
enum action { Shift, Read};
action A = Read; Token tok;
while (tok != END)
{
if(A == Read) tok = gettoken();
if(A == shift(tok)) A = read;
else if(A == reduce(tok)) A = shift;
else if(tok == END) return ACCEPT
else return ERROR;
}
--EDIT--
Awww, no indentation support in HN.
while (1) {
print "yay";
}