Why is it not very common to use do/while?

Asked

Viewed 441 times

4

I don’t know if this occurs only in PHP, but I realize that it is not very common the use of do/while in the codes written on it.

Example:

while (ob_get_level() > 0) {
    ob_end_clean();
}

That (I believe) could be done so

do {

   ob_end_clean();

} while(ob_get_level() > 0);

Why the use of while is more common than do/while?

We could say that do/while not as useful?

  • 10
  • 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.

  • 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 :)

  • The question is different, but the answers may go in the same direction. That’s a problem?

  • 2

    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.

  • 1

    You can reply @mgibsonbr :). There’s nothing to stop

  • 1

    There’s this other, Why use while(0)? which is already a different context.

  • 1

    @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

  • 1

    that this @mgibsonbr! I think they were just waiting for you to close the account!

Show 4 more comments

3 answers

8

The two constructions are completely different, they don’t do the same thing. So this probably explains why one is more used than the other. Probably the second form is less necessary than the first.

The first will execute zero or more. The second will execute one or more often depending on the condition, of course. But the second ensures a block execution while the first may not perform anything.

You can say that in some specific situation within a larger context of the application you can ensure that the first construction will perform at least once, but there is something dependent on the context.

The semantics of the second is clear that must necessarily execute once, without relying on anything. Even if you can be sure that the first one will run once in a given context, this may change in the future because its semantics is to allow no execution to occur.

Always understand the problem and write the construction that expresses the real need for the code, even if, by coincidence, another construction can work in that context.

8


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 ifs 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...)

  • 2

    Where is the applause Emoticon here? It has happened to me several times that I start with a do..while, happy to have this option, and then end up replacing it with while. I was missing understand why and now I have an idea and material to think.

4

The do/while ensures that the code block is executed at least once.

In the case of while, a condition is tested before the code block is executed.

Although they are different, I venture to say that the while is most used simply for easy reading. While in do/while you will need to go down in the code to understand what condition that code in the block is obeying, on while you see this immediately. I believe it is more natural for the developer.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.