This suggestion from the PBP is simple: Don’t use do … while loops. The reasoning is good and readable alternatives are provided. I wind up having no trouble with this suggestion, although I wasn’t as happy with it when I first read it.
When I first read the PBP, my C programmer was still showing much more than today. In C, a do/while is a first-class loop and all the loop control structures work as expected. I hadn’t realized that it was just a postfix while on a do {} block in Perl, and how much that mattered. Despite Mr. Conway’s clear examples of how problematic this could be, I felt it was still a useful tool.
In the years since, I’ve rarely, if ever, reached for the tool. It’s easy enough to avoid it, and prevents some ugliness. It also means you’re writing things with all the assumptions in the condition at the top, rather than putting the decision at the bottom of the block where it’s harder for a person to read. Those things became more important to me over time, as I worked with larger teams and with more people who were not from a C background.
So, I follow this suggestion. I do it because it’s useful and practical, not out of any kind of zealotry.
[Note: I missed a week! Sorry about that. I was moving, and other things kept demanding my time. The worst of that is hopefully behind me!]
I’ve never really liked this recommendation of Damien’s… every now and then I run into a case where I want to do a structure like “repeat this until you get it right” (command line user input is one case) and I think it reads more naturally to put the exit condition at the end of the loop. It is a problem that these aren’t “first class” loops, but there’s a documented work-around: double up the braces. This kind of thing works: do{{ … next if … }} while ;
I do not follow this rule, but it’s a distinction without a difference because I essentially never write
do {} while
loops anyway. I find that almost every single time I need an unconventional loop structure, it’s because I really want to check the condition in the middle of the loop body – not at the top, but nor at the bottom.For a contrived example, consider the least repetitive way to implement
join()
completely imperatively:1. Pull an item from the input array.
2. If it is undefined, make it an empty string.
3. Add it to the output string.
4. End the loop if no items are left.
5. Add a separator to the output string.
6. Do another loop iteration.
If you try to write this with a conventional loop structure, there will be somewhere that you have to repeat that condition which is also used to terminate the loop. The only way to write that condition just once is a loop that checks its condition in the middle, so that some of the work is done always, but some of it only after the loop condition was true. This can be done – awkwardly – with an infinite loop and a
last
in the middle; I do not like that approach. It can also be done with a naked block with alast
in the middle andredo
at the end, but I like that even less. Therefore much of the time I have this dilemma, I end up just accepting some repetition; e.g. in thejoin()
example, the fact that there even is repetition is going to be invisible to 99.9% (or more) of programmers.do { … } while *isn’t* a postfix modifier on a do {} block, though. It just looks that way.
Compare:
perl -le’do { print “here” } while 0′
vs.
perl -e’print “here” while 0′
That first one generates output, the second one doesn’t. The condition check happens after the first iteration in the do/while construct.