Help

So the comment thread of my previous post got me thinking again about the do/while statement. Frankly, it's difficult to see why we really need this as a first-class construct in modern programming languages. Here's my list of reasons for saying that.

First, at least in C-like languages, the syntax is pretty irregular. It's the only control statement that doesn't follow this syntax pattern:

( keyword ("(" Something ")")? Block )+

Instead its syntax features two glorious keywords and a stray - totally extraneous - semicolon. Weird.

('Cos, like, even with the help of two keywords and a pair of parens, the parser still needs that extra juicy semicolon to know when to stop looking for more bits of the do/while statement. Sure, that makes sense.)

Anyway, I figure that it's this irregularity that apparently leads different people to have different intuitions about the scope of declarations contained in the body of the do/while.

Second, do/while demands that we reserve as a keyword one of the most useful verbs in the English language. I could have found all kinds of uses for the word do if Java would just let me use it as an identifier!

Third, do/while is easily emulated using a while loop. The following:

do {
    something();
}
while (!finished);

is only somewhat less readable when written like this:

while (true) {
    something();
    if (finished) break;
}

And this second formulation doesn't open up any debate about the scope of things.

Fourth, the current crop of programming languages all have support for higher-order functions, allowing libraries to introduce new kinds of flow control, and taking pressure off the language itself to provide every possible kind of loop baked into its basic syntax.

Finally, even though I've written a reasonably significant amount of Java code in over more than a decade of familiarity with the language, I can barely recall ever having found a use for this construct. Unless my memory is even worse than I think it is, I can't possibly have used do/while more than two or three times in my entire career!

So I'm strongly considering simply dropping do/while from Ceylon. It's simply not pulling its weight.

22 comments:
 
19. Jul 2011, 07:42 CET | Link

You are making a good case against it and I agree. If my memory serves me right, I have never used a do/while loop. In the end the only difference between do/while and while is the fact that the body of a do/while loop is executed at least once. As you pointed out, there are techniques to work around this difference. So, under these circumstances, a do/while construct looks redundant to me and that does not lead to clear, easy to read code which you are aiming for with Ceylon.

ReplyQuote
 
19. Jul 2011, 08:53 CET | Link
Timothy Whitehead

My vote would be to leave it out of the language. I, like you, have almost never used it and the workaround with if (finished) break; is more than sufficient, if not better as the statement does not have to be at the end nor be the only break.

For some reason this discussion made me think of this other common structure that I always wanted a better way of doing

boolean found = false;
for (Record record : getRecords() ) {
 if (record.field == desired value) {
  dosomething();
  found = true;
  break;
 }
}
if (!found) {
 dosomethingelse();
}
 
19. Jul 2011, 08:56 CET | Link

1

Dustbin it.

 
19. Jul 2011, 09:23 CET | Link
Timothy Whitehead wrote on Jul 19, 2011 02:53:
For some reason this discussion made me think of this other common structure that I always wanted a better way of doing
boolean found = false;
for (Record record : getRecords() ) {
 if (record.field == desired value) {
  dosomething();
  found = true;
  break;
 }
}
if (!found) {
 dosomethingelse();
}

Ahyes, you're looking for a for/fail loop, which makes a brief appearance here. (It might end up being a for/else loop like in Python, but whichever way we go it will do what you're looking for.)

(P.S. I just quickly checked the test suite for the compiler backend and yes indeed, someone - Tako, it looks like - already implemented for/fail all the way.)

 
19. Jul 2011, 11:31 CET | Link

Drop it like its hot, do/while is redundant not to mention ugly.

 
19. Jul 2011, 11:36 CET | Link
Georg Ragaller
I personally use this construct also very seldom, almost never. I even prefer for over while, but that's my taste.
To get some numbers I ran the following on a codebase at work which is not too small and it
confirmed me that the do/while is not used very often:


bash-4.1$ find . -type f -name \*.java | xargs cat > all
# count all lines
bash-4.1$ wc -l all
1107640 all
# count all but empty lines
bash-4.1$ cat all | sed '/^\s*$/d' | wc -l
957900
# count do's of a do/while
bash-4.1$ egrep '^\s+do\s*$' all | wc -l
20
# count all whiles
bash-4.1$ egrep '^\s+while\s*\(' all | wc -l
2307
# interestingly I saw a lot of while(true) i.e. forever loops, so I also
# counted them and was really staggered
bash-4.1$ egrep '^\s+while\s*\(' all | egrep 'while\s*\(\s*true\s*\)' | wc -l
838
bash-4.1$


So to cite Joshua Bloch "When in doubt, leave it out"
 
19. Jul 2011, 13:47 CET | Link
Daniel Yokomizo | daniel.yokomizo(AT)gmail.com

Sather generalize all forms using a single loop control and iterators with coroutine-like semantics.

 
19. Jul 2011, 13:49 CET | Link
Thorsten
Timothy Whitehead wrote on Jul 19, 2011 02:53:
My vote would be to leave it out of the language. I, like you, have almost never used it and the workaround with if (finished) break; is more than sufficient, if not better as the statement does not have to be at the end nor be the only break. For some reason this discussion made me think of this other common structure that I always wanted a better way of doing
boolean found = false;
for (Record record : getRecords() ) {
 if (record.field == desired value) {
  dosomething();
  found = true;
  break;
 }
}
if (!found) {
 dosomethingelse();
}

In Smalltalk that's simply:

records 
    detect: [:record | record field = desiredValue] 
    ifFound: [:record | someone doSomething] 
    ifNone: [someone doSomethingElse]

Smalltalk does nicely without any constructs for looping: no do-loop, no do-while-loop and not even a for-loop -- they are all not needed thanks to closures and the nice syntax. The same should probably be possible in Ceylon.

So, do away with the do-while loop (and the other loops :-)

 
19. Jul 2011, 13:52 CET | Link
Neil McF.

Agree 100% with dropping it. KISS and all that.

 
19. Jul 2011, 15:27 CET | Link

I think removing it a great call. I believe the only times I've used it, has been a knee jerk reaction that I 'should' use it since I'm performing a check at the end of the process.

 
19. Jul 2011, 17:12 CET | Link
Norbert

This may be out of scope, but in the back of my head I kept wondering why, in Ceylon, the keyword is fail (It is a keyword, isn't it?) when a for-loop actually runs through without a break. It seems, implicitly, Ceylon considers breaking a loop to be the standard way-to-go. But isn't that only the case when the loop is used for searching?

Boolean minors;
for (Person p in people) {
    if (p.age<18) {
        minors = true;
        break;
    }
}
fail {
    minors = false;
}

The above sample from part 7 bugs me, because I think that the default value for minors can be setup at the point of declaration. Why is the following not as good? Why do you need fail at all?

Boolean minors = false;
for (Person p in people) {
    if (p.age<18) {
        minors = true;
        break;
    }
}

If you're determined to keep this clause, couldn't you rather call it default? (Sorry, if this should have put into the comments of part 7.)

 
20. Jul 2011, 07:04 CET | Link

I wouldn't miss do/while. And I would use for/fail a lot! Another common loop idiom:

Boolean firstTimeRound = true;
for (Person p in people) {
   if (firstTimeRound) {
      ...do something...
      firstTimeRound = false;
   } else {
      ...do something different...
   }
   ...do main bit of loop...
}

Is there any shorthand for that?

 
20. Jul 2011, 07:17 CET | Link

What about removing 'continue' as well? It can be replaced with 'if' (reversing the condition).

As for 'break': it's very useful. All loops can be written as follows:

while (true) {
    something();
    if (finished) break;
    more();
}

But this syntax can be simplified. Because 'break' is always conditional (once you get rid of 'continue'), it would make more sense to write:

while (true) {
    something();
    until finished;
    more();
}

Some statistics:

186482 lines
  2239 for (...)
   461 while (...) (excluding 'while (true)' and 'do .. while')
   256 while (true)
    77 do ... while
  1728 break;
   186 continue;
 10245 if (...)
  2784 else
   283 switch
  2443 case
 
20. Jul 2011, 07:41 CET | Link
If you're determined to keep this clause, couldn't you rather call it default?

I believe the consensus so far is to rename it to else, which is fine by me.

 
20. Jul 2011, 07:45 CET | Link
What about removing continue as well? It can be replaced with if (reversing the condition).

Well, not quite. In a very complex loop body with nested control structures, continue can be simpler than an if.

However, I admit that I never use it in practice. In fact, it simply never occurs to me to use continue.

 
20. Jul 2011, 07:49 CET | Link
Another common loop idiom:
Boolean firstTimeRound = true;
for (Person p in people) {
   if (firstTimeRound) {
      ...do something...
      firstTimeRound = false;
   } else {
      ...do something different...
   }
   ...do main bit of loop...
}
Is there any shorthand for that?

Hrm, is this really common? I must admit it doesn't strike me as something super-familiar. Can you show us a more realistic example? I was thinking perhaps you're doing some kind of fold, but even that doesn't quite fit.

 
20. Jul 2011, 08:34 CET | Link

Okay maybe it's just common to me :)

But for example writing out a comma-delimited list:

ResponseWriter writer = ctx.getResponseWriter();
Boolean firstTimeRound = true;
for (Person p in people) {
   if (firstTimeRound) {
      // No comma on first entry
      firstTimeRound = false;
   } else {
      writer.writer(", ");
   }
   writer.write(p.name);
}
 
20. Jul 2011, 09:10 CET | Link
But for example writing out a comma-delimited list

Ahyes, that one I recognize, but, strangely enough, I always think of it has handling the last element differently ;-)

Hrm, I think our Sequence interface is quite nice for this problem:

String[] names = people[].name;
void write(String s) = writer.write;
if (nonempty names) {
    write(names.first);
    for (name in names.rest) {
        write(", ");
        write(name);
    }
}
 
20. Jul 2011, 09:16 CET | Link

P.S. The code above could also have been written:

value names = people[].name;
value write = writer.write;
if (nonempty names) {
    write(names.first);
    for (name in names.rest) {
        write(", ");
        write(name);
    }
}

Though I think it's more difficult to understand like that. Type inference has it's downside.

(Oh and 10 points to the first person who can tell me what is the inferred type of the local write haha.)

 
21. Jul 2011, 00:17 CET | Link
Stef Epardaud

Yeah this is the sort of thing fold does automatically for you:

T fold<T>(T f(T x, T y), T initVal, T[] list){
 if(nonempty list){
  return f(list.first, fold(f, initVal, list.rest));
 }else{
  return initVal;
 }
}

String join(String sep, String[] list){
 return fold{
  String f(String x, String y){ return x + sep + y; }
  initVal = "";
  list = list;
 };
}

value test = join(", ", {"a","b","c"});
 
21. Jul 2011, 09:25 CET | Link
Quintesse
(P.S. I just quickly checked the test suite for the compiler backend and yes indeed, someone - Tako, it looks like - already implemented for/fail all the way.)

Yes, and the rename to else instead of fail is implemented as well.

 
22. Aug 2011, 01:37 CET | Link

Please vote for Kotlin and Ceylon merging in Kotlin bug tracker. Vote

Post Comment