Perhaps because it is simpler to determine the invariant loop through the while
(and of for
) than of do..while
. And invariant is something that is always necessary to understand the semantics of the code, whether it is placed explicitly or implicitly (i.e. only in the programmer’s head).
Invariant
Every code [imperative] has a set of variables that progress from the state E0
pro E1
, pro E2
, etc, until reaching the final state En
. Typically, the state is affected by each single instruction, and one can summarize its set of values before and after each instruction:
{}
x = 10;
{ x = 10 }
y = 20;
{ x = 10, y = 20 }
z = 2*x + y;
{ x = 10, y = 20, z = 40 }
x = 30;
{ x = 30, y = 20, z = 40 }
...
When including loops, however, it is not possible to establish the exact values of the variables, as each instruction can run several times and the state will be different between one and the other:
{}
a = 3;
{ a = 3 }
b = 5;
{ a = 3, b = 5 }
r = 0;
{ a = 3, b = 5, r = 0 }
while ( a > 0 ):
{ a = ?, b = 5, r = ? }
r += b;
{ a = ?, b = 5, r = ? }
a--;
{ a = ?, b = 5, r = ? }
{ a <= 0, b = 5, r = ? }
return r;
To understand the state at the end of the execution (implicitly, everyone must have realized that r
will contain the product a * b
, right? ), if you use the invariant technique: establish a condition that must be true before and afterward of the loop run:
{ r = (3-a)*b }
Check that in fact it is true before:
{ a = 3, b = 5, r = 0, r = (3-a)*b = (3-3)*5 = 0 } OK
And that if it is true later, it will contain the desired result:
{ a = 0, b = 5, r = (3-a)*b = (3-0)*5 = 3*5 } OK*
Finally, make sure - with each loop run - the invariant starts true and ends true:
{ a = a0, b = 5, r = (3-a)*b = (3-a0)*b } OK, Premissa
r += b;
{ a = a0, b = 5, r = (3-a0)*b + b } X
a--;
{ a = a0-1, b = 5, r = (3-a0)*b + b = (3-a0+1)*b = (3-(a0-1))*b = (3-a)*b } OK
So we have proof that the above code will actually produce the expected result.
* Note: missing here prove that, starting from a a
positive integer, it will exit the loop as zero, and not as any number less than zero. As the body of the loop decreases a
only once, there’s no way he can be negative, but unfortunately I don’t know how to formalize this...
Applying to the while
and to the do..while
The analysis technique described above is a type of formal method, but even if such formalism is rarely used in practice, at least in the programmer’s head a similar analysis is made, to understand why at the end of a loop the program will be in a state that corresponds to the desired computation result. Many bugs are due to the person not realizing that such a condition that was supposed to be true during and after a loop is actually not (leading to the tedious process of debugging the loop step by step on Debugger, or maybe printing variable values on the screen conforms the loop advances).
In a well structured program the function of each variable is clear, the progress of the states is simple and homogeneous, and although the invariant is not formalized the programmer can notice that something "stays" between one execution of the loop and another. But what about the do..while
, that same reasoning would not apply?
The problem of do..while
is that - as pointed out in the other answers - it arises in situations where it is obligatory that the loop body performs at least once. Why is it mandatory? Usually because it has some variable that has not been initialized right, a value that may or may not be null, etc. Because if none of this were true, if the state of the program was perfectly valid even if the loop did not run at all, there would be no need for the do..while
.
Thus, the analysis of a code that uses do..while
is more laborious than one that does not use. Because at first, in the first loop iteration the invariant is not necessarily true yet, while in all the following it must be. Consequently, it is necessary to write a body that simultaneously: 1) takes the program from a state in which the invariant is false to one in which the invariant is true; 2) with the same instructions, part of a state in which the invariant is valid for another that it continues to be valid. This is complicated, and ends up requiring you to think about each line of code twice (in the first iteration will happen this, in the others will happen that), including the possibility of introducing if
s to deal in a differentiated way with the first iteration and the others.
break
, continue
, return
etc..
Incidentally, this same problem occurs when a loop is interrupted "abruptly" in the middle of its execution (via break
, continue
, return
) or even when a function terminates prematurely. Surely you must have seen some "advice" suggesting to avoid these constructions, just as everyone has been warned regarding the goto
. Flow breaks mask/invalidate the invariant...
My conclusion, however, is the same as in other cases: use do..while
when it makes sense, but make sure you understand well the progression of states between one iteration and another, and guard against any possibility of leaving your program in an indefinite state. In my personal experience, this often implies refactoring the code, which often ends up leading me to - you see - replace the do..while
by a while
! This has happened so many times that, in designing a new code, I start by paying attention to certain patterns, moving "special" code out of the loop, and the natural consequence is to fall into a situation where the while
is the most appropriate tie to my case.
(Finally, an addendum: often the only function of a loop is to iterate over a collection, in which case there are constructions more appropriate to it than a loop - foreach
, map
, etc, in particular because they simplify the special case of the collection being empty; in others, the loop works very closely with an index or counter, so that it is easier to control the minimum number of iterations through the conditions involving the index than via a different loop structure; the only case where a do..while
it seems natural - and this case is rare - is when the logic of your code is "try to do something, and if it doesn’t work try again", a typical application of Polling for example, something increasingly rare today...)
Related: What is the usefulness and importance of "do... while"?
– rray
At first I thought it could be based on opinions, but I decided to respond objectively ignoring what could imply opinion, which did not run with the other answers. Now I’m thinking of duplicate, I remember the question but I didn’t remember that the answer was the same.
– Maniero
If you were to think of it as "Why it is not used" and "What is the use" you could refer to the same answer. If you think you should dial as a duplicate, I’ll vote for you myself :)
– Wallace Maxters
The question is different, but the answers may go in the same direction. That’s a problem?
– Wallace Maxters
If the question is not closed, I would like to try to answer based on formal methods - me suspect (but I still can’t say for sure) that it’s easier to understand the semantics of a code that runs zero or more times than one that runs one or more times.
– mgibsonbr
You can reply @mgibsonbr :). There’s nothing to stop
– Wallace Maxters
There’s this other, Why use while(0)? which is already a different context.
– rray
@Wallacemaxters Haha is that I knew that such an answer would take a while, and I saw the people talking about closing, if I did not express myself soon and this happened, when I had just formulated the answer I could no longer post... : P
– mgibsonbr
that this @mgibsonbr! I think they were just waiting for you to close the account!
– Wallace Maxters