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.
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.
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(); }1
Dustbin it.
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.)
Drop it like its hot, do/while is redundant not to mention ugly.
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"
Sather generalize all forms using a single loop control and iterators with coroutine-like semantics.
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 :-)
Agree 100% with dropping it. KISS and all that.
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.
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.)
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?
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 caseI believe the consensus so far is to rename it to else, which is fine by me.
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.
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.
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); }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); } }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.)
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"});Yes, and the rename to instead of is implemented as well.
Please vote for Kotlin and Ceylon merging in Kotlin bug tracker. Vote