You may wonder what kind of situation would require something that traditional constructs can't handle. Here is an example taken from Go To Statement Considered Harmful: A Retrospective.
int parse()
{
Token tok;
reading:
tok = gettoken();
if (tok == END)
return ACCEPT;
shifting:
if (shift(tok))
goto reading;
reducing:
if (reduce(tok))
goto shifting;
return ERROR;
}
Let's see how using a while turns out:
type Token = END
type ParseState = Reading | Shifting | Reducing | Accepted | Errored
type Status = ACCEPT | ERROR
let parse gettoken shift reduce () =
let mutable state = Reading
let mutable tok = Unchecked.defaultof<Token>
while state <> Accepted && state <> Errored do
state <-
match state with
| Reading ->
tok <- gettoken()
if tok = END then
Accepted
else
Shifting
| Shifting ->
if shift tok then
Reading
else
Reducing
| Reducing ->
if reduce tok then
Shifting
else
Errored
| Errored | Accepted -> failwith "Invalid state"
match state with
| Errored -> ERROR
| Accepted -> ACCEPT
| _ -> failwith "Non-final state"
(Note that I have introduced extra parameters gettoken, shift and reduce; I wanted to be able to write concise code that syntactically correct without explicitly defining these functions).
It looks OK, but we had to introduce an extra type, ParseState. There are also some issues with mutability and initialization of tok.
If you have some experience with functional programming, you are probably thinking that recursion is the right tool. I will leave writing a recursive version of parse to the reader, and jump to a slightly different idea that many might have missed.
type Token = END | SOMETHINGELSE
type Status = ACCEPT | ERROR
let parse gettoken shift reduce () =
let rec do_read() =
match gettoken() with
| END -> ACCEPT
| _ as tok -> do_shift tok
and do_shift tok =
if shift tok then
do_read()
else
do_reduce tok
and do_reduce tok =
if reduce tok then
do_shift tok
else
ERROR
do_read()
do_read, do_shift and do_reduce are mutually recursive functions. All recursive calls are in tail position, which makes it possible to avoid stack overflows (on some platforms...).
Here you have it: calls in tail position are basically safe versions of goto.
The fantastic control flow construct that many have forgotten is simply the procedure call. Every programmer uses it a countless number of times every working day, but fear of recursion and stack overflows have kept many from using it in situations where it fits well.