There have been a few posts on loops as of late, which reminded me of something I’ve ran into before. In legacy code I often run into a block that looks something like this.
condition = .false.
do i = 1, 10
......
if (condition) goto 20
enddo
error stop "Condition not met"
20 continue
I generally just leave these code blocks alone, as refactoring them to remove the goto feels error prone and not entirely sure it makes the code more readable.
Python has a really handy for/else construct, where if a loop completes it will execute a code block. Python For Else
I think some similar to this would be a great addition to Fortran. Feels it could be as simple as
condition = .false.
do i = 1, 10
.......
if (condition) exit
else
! This block only executes if the do loop doesn't exit.
error stop "Condition not met"
enddo
I’m not aware of any obvious way to replicate this behavior without gotos in the current standard.
conditional: block
do i = 1, 10
! ...
if (condition) exit conditional
end do
! Loop did not exit prematurely as anticipated
error stop "condition not met"
end block conditional
When I asked ChatGPT 4o, “in Fortran, how would you rewrite the [OP’s first code] without a goto and a numbered line?”, it gave effectively the same answer as @PierU
condition = .false.
do i = 1, 10
......
if (condition) exit
enddo
if (.not. condition) then
error stop "Condition not met"
end if
and exactly the same answer when asked to shorten the code, as does perplexity.ai and Claude 3.5 Sonnet.
You can also test the loop control variable. For example:
condition = .false.
do, i=1, 10
…
if (condition) exit
end do
if (i > 10) then
error stop “condition not met”
end if
More generally though, a proposed structured control construct back in the 1970s was the so-called situation case. Charles Zahn wrote some papers on this, and Donald Knuth picked up on it. Zahn also used the MORTRAN Fortran preprocessor with macros to accept his SKOL language. SKOL supported the situation case. See: Zahn's construct - Wikipedia
This block seems like a do while construct would better portray the intent:
condition = .false.
i = 1
do while (.not. condition)
if (i > 10) error stop "Condition not met"
......
i = i + 1
end do
To me, the intent seems more like “do a thing until a condition is met but also terminate if it takes too long” rather than “do a thing for x iterations but the thing is only accepted if not all x iterations are required”. This intent can be further illustrated by including a max_iters variable:
condition = .false.
i = 1
max_iters = 10
do while (.not. condition)
if (i > max_iters) error stop "Condition not met"
......
i = i + 1
end do
This method becomes cumbersome if you have many conditions and/or want to keep track of which condition is met however. There is also the issue that it is very easy to forget to include either of the initializations of i and max_iters and to include the increment statement i = i + 1, where these errors would get caught by the compiler in a normal do loop. But I personally prefer this method for this type of situation.
No, Fortran does not have constructs with fall through. As you said, all other conditional constructs execute at most one block of code. With that point, I’m really not sure I like this idea.
A minor tangent, but people have also been asking for exceptions for a long time, and if we followed the Python model there you’d end up with some really hard to follow control flow, i.e.
In Knuth’s classic paper “Structured Programming with go to Statements”, the single exit found/not_found search loop in this thread is his first example. He talks about several alternative ways of coding it. Midway into the paper he introduced Zahns situation case, he calls them ‘event indicators’, which can more generally handle loops with multiple exits. He also showed the construct in non-loop form - though without any examples.
(It is amazing to me that this paper was written 50 years ago! My has time flown by… Back then, case-like constructs were just starting to be recognized as necessary for structured programming. Only a few languages, like BCPL and BLISS, had the construct. Even elseif was controversial.)
Nowadays I think just using an extra variable to indicate which subset of a loop is being executed, a conditional EXIT after each subset, followed by a SELECT CASE after the loop, is fine. Same with a non-iterative BLOCK/END BLOCK - where one could do multiple EXITs from the block. E.g., something like:
integer :: step
…
step = 0 ! if n < 1
do, i=1, n
step = 1
…
if (…) exit
step = 2
…
if (…) exit
step = 3
…
end do
select case (step)
case (1)
…
case (2)
…
case (3)
…
case default
…
end select
Something like this can handle the Duplicate Code problem. It might also dovetail nicely with the F2023 enumeration types.
Many other languages have a try/throw/catch construct for exception handling. One could use it for a simple loop. (In fact a friend of mine, who years ago co-wrote much of Sun’s javac compiler, once told me that it was more common than he liked.) Except that exception handling usually implies a much more far-reaching capability than a simple loop or block.