Beefy Boxes and Bandwidth Generously Provided by pair Networks
good chemistry is complicated,
and a little bit messy -LW
 
PerlMonks  

do/redo or for(;;): what's Kosher?

by rje (Deacon)
on Jan 03, 2002 at 22:26 UTC ( [id://136026]=perlquestion: print w/replies, xml ) Need Help??

rje has asked for the wisdom of the Perl Monks concerning the following question:

I know there's legitimately more than one way to do things in
perl. However...

A couple months ago a veteran perler here posted his favorite
"eternal-block" control structure:

{ # do stuff last if BREAK-CONDITION; redo; }
I thought this looked really, really cool. I've even used it
in a script I recently wrote. I was wondering, however, if it's
considered bad style to do "eternal loops" this way?

I really like it, and am likely to abandon the for(;;) construct
permanently in favor of the above idiom. Should I repent?

rje

Replies are listed 'Best First'.
Re: do/redo or for(;;): what's Kosher?
by tune (Curate) on Jan 03, 2002 at 22:42 UTC
    I prefer
    while (1) { last if...; }
    It's more readable (IMHO).

    --
    tune

      Absolutely agree. Personally I find: the Perl way of doing things is either while() or for (@list). This is for a variety of reasons.

      First of all, if you have a loop, it should be obvious that it is a loop. (To take it to extremes, that's what BASIC's GOSUB did wrong - it is a subroutine call, but the called place doesn't look like a subroutine.)

      Also, the for(;;) form is to be avoided; most of the time you can write it as for (@list). Perl understands the notion of a list natively so you should not concern yourself with index variables. This eliminates an entire class of coding errors - the fencepost errors which consist in mostly overlooking nasty special conditions that result in the loop executing once more or less than it should. for(;;) also is inefficient (internally, it is handled like a while () {} continue {} construct).

      { ... ; redo } has its place just like for(;;) is occasionally useful - but for the sake of your own sanity you should not make either of them a habit.

        And here's the benchmark to prove it:
        #!/usr/bin/perl -w use strict; use Benchmark; Benchmark::cmpthese(10_000, { 'C-style' => sub { for (my $i = 0; $i <= 1000; $i++){ undef } }, 'foreach' => sub { for my $i (0..1000) { undef } } }); __END__ Benchmark: timing 10000 iterations of C-style, foreach... C-style: 5 wallclock secs ( 5.20 usr + 0.00 sys = 5.20 CPU) @ 19 +23.08/s (n=10000) foreach: 4 wallclock secs ( 3.73 usr + 0.00 sys = 3.73 CPU) @ 26 +80.97/s (n=10000) Rate C-style foreach C-style 1923/s -- -28% foreach 2681/s 39% --

        By the way: does perl have absolutely _no_ optimisation for for (;;) without any expressions in its parens? I find that a bit hard to believe.

        2;0 juerd@ouranos:~$ perl -e'undef christmas' Segmentation fault 2;139 juerd@ouranos:~$

(tye)Re: do/redo or for(;;): what's Kosher?
by tye (Sage) on Jan 04, 2002 at 00:08 UTC

    I find that last, next, and redo can lead to code that is very hard to read/maintain, etc. They are very useful and I use them myself but I try to avoid them and I pretty much refuse to use them in large, complex, and/or nested loops. If I can't see the whole loop on one screen and easily pick out the last, next, and redo, then I don't like to use them.

    If I find a need for these in such cases, it usually means that I need to break the code up into smaller subroutines and these usually disappear once I do that. Sometimes they are replaced by an early return from a subroutine which is something I don't feel any need to try to avoid.

    I really dislike using a naked block with redo as a "loop". It feels mostly like using goto to make a loop. They are both very powerful; there isn't any control flow that can't be constructed with either. That is a nice feature, but it is also a liability. I've seen several cases of good programmers that jumped directly to naked-block+redo and produced code that worked but was quite complex and confusing that I was able to turn into a very simple while loop. Because naked-block+redo allows you to create any control structure, no matter how complicated, you can implement your first stab and not notice that you've overlooked a much simpler, cleaner, clearer solution.

    Even if you start with a fairly simple and small naked-block+redo "loop", on-going maintenance can shove things around. last and redo can end up being like a goto but worse because you don't have a lable so you can't easily tell where control is being passed to. You have to keep scanning up looking for blocks and seeing which blocks are loops (vs. if blocks, do blocks, etc.) and then perhaps find the matching end of the block.

    I also agree with others here that don't think "loop" when they see a naked block and so I don't think I'll ever use a naked block that way in code that will be maintained.

            - tye (but my friends call me "Tye")
Re: do/redo or for(;;): what's Kosher?
by dws (Chancellor) on Jan 03, 2002 at 22:45 UTC
    I really like it, and am likely to abandon the for(;;) construct permanently in favor of the above idiom. Should I repent?

    One of the tradeoffs you'll need to consider when making this decision is whether readability suffers when your loop gets to be more than an eyefull. A non-loop scope is a common idiom, particularly when the intent it to temporarily localize variables. When I read

    { local $/ = undef; ...
    I think to myself, "ah, slurp mode," not "I wonder if this is going to be a loop?" I then read on with a mental context set. But if 15 lines down I enounter last or redo, I have a moment of confusion to work through that wouldn't have been there if the scope had started with   while ( 1 ) { or   for ( ; ; ) {
Re: do/redo or for(;;): what's Kosher?
by SparkeyG (Curate) on Jan 03, 2002 at 22:42 UTC
    Something I would use instead of your "eternal-block" would be:

    do { #do stuff #More stuff #etc } until ( BREAK-CONDITION);
    It strikes me as easier to understand what's going on at a quick glance.

    --SparkeyG
    Perl Programming Dad

      That would be fine, except last/next/redo don't respect that as a "loop" because that's not a real loop-block. That's why I came up with the other version: it's a lean, mean, looping machine!

      -- Randal L. Schwartz, Perl hacker

        Just curious. Why would you prefer this:

        { # do stuff last if BREAK-CONDITION; redo; }

        to this:

        { # do stuff redo if !BREAK-CONDITION; }

        or even (just to be pedantic),

        { # do stuff redo unless BREAK-CONDITION; }

        dmm

Re: do/redo or for(;;): what's Kosher?
by Masem (Monsignor) on Jan 03, 2002 at 22:45 UTC
    There's certainly nothing wrong with that block structure; as you mentioned, there's multiple ways to set up such a block in perl.

    As for 'repenting', I think you need to simply judge if a block style set up as a infinite loop is best, or if a block style set up as a very long but not infinite loop is better, and this is a matter of perl style. For example, if my program is continuously accepting user input, I'd consider that as a infinite loop and thus would make sense for a do-last if-redo block. On the other hand, if I'm reading in a data file, which has some fixed size though may be in the gigs, I'd use a while or similar loop to handle it.

    But, consider your audience for the code; if it's your workplace, make sure that the structure is understood before converting everything over to that. Make sure to comment well because if there's a lot of ccode between the two braces, it'll look like a bare block instead of a control structure; ideally, that type of block should only last a few lines as to be able to see the redo at the end while viewing the top brace. If this is a problem, there are other control structures that effectively do the same thing.

    -----------------------------------------------------
    Dr. Michael K. Neylon - mneylon-pm@masemware.com || "You've left the lens cap of your mind on again, Pinky" - The Brain
    "I can see my house from here!"
    It's not what you know, but knowing how to find it if you don't know that's important

Re: do/redo or for(;;): what's Kosher?
by dragonchild (Archbishop) on Jan 03, 2002 at 22:42 UTC
    I personally don't like that idiom, because it's not obvious to me that it's an infinite loop. For that, I would prefer while (1) or something along those lines. (Actually, I've never had to program an eternal loop before. *shrugs*)

    Of course, I've also never used redo, so the idiom isn't very familiar to me.

    ------
    We are the carpenters and bricklayers of the Information Age.

    Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

      ...it's not obvious to me that it's an infinite loop...

      Its easy to see on a short, simple loop, but on longer more complex things I just label loops as such (or whatever they function as):

      # I usually include the label in the loop control statement # so everything's immediately obvious, but I omit them here. LOOP: { last if $something; ... redo; } INFINITELOOP: { ... redo; } FILTER_FOO_LOOP: { next if foo(); ... redo; } FOO_SWITCH: { foo() and last; foobar() and last if something(); }
      The one time I've used redo is when I've wanted to do just that -- redo something that I was only expecting to have to do once, but that messed up. Connecting to UPS's shipping services, some of the possible errors returned are 'transient' and can be fixed by simply resending the quote request. So i have something along the lines of:
      "redo SUBMIT_REQUEST if $transient_error"
Re: do/redo or for(;;): what's Kosher?
by Juerd (Abbot) on Jan 03, 2002 at 22:51 UTC

    You're right about the TIMTOWTDI:
    for (;;) { ... }
    while (1) { ... }
    do { ... } while 1;
    do { ... } until 0;
    { ...; redo; }

    Everyone chooses their own favorite idiom.
    Perhaps the bare-block-with-redo-solution is the "best" one, but it doesn't look clear. Imagine having a 1000 line loop (there's something wrong with the program design if that's the case, but let's forget that) and seeing only { on the top. In that case (imnsho) it's better to use while (1) { ... } or for (;;) { ... }.
    (I admit: I made the same mistake.)

    By the way, why would you abandon for (;;) { ... }? A C-style for with only ;; can mean only one thing, and that's why i think it's the "best" solution.

    2;0 juerd@ouranos:~$ perl -e'undef christmas' Segmentation fault 2;139 juerd@ouranos:~$

      I know it's more code, but my favorite method for obvious loops looks like this:
      $forever = 1; while ($forever) { ... }
      ---
      Spring: Forces, Coiled Again!
        That won't be optimized away, but a constant true value (not 0, "0", "" or undef) are.
        This means you can have less code and more efficiency, but still the clarity you like:
        while ('forever'){ ... }


        Which is equivalent to while (1) { ... }, which is optimized to for (;;) { ... }.

        2;0 juerd@ouranos:~$ perl -e'undef christmas' Segmentation fault 2;139 juerd@ouranos:~$

Re: do/redo or for(;;): what's Kosher?
by derby (Abbot) on Jan 03, 2002 at 22:45 UTC
    rje,

    I've always been partial to:

    while( 1 ) { # do stuff last if BREAK-CONDITION }

    sure you have to re-evaluate the conditional but that's a price I'm willing to pay for better readability. redo inside one block seems silly and if I used it deep in nested blocks (with a label) I'd feel dirty.

    -derby

Re: do/redo or for(;;): what's Kosher?
by Lord Wrath (Friar) on Jan 04, 2002 at 01:57 UTC
    I read through all the responses here, and it seems to me that a couple comments would suffice to reduce confusion on what this block does. Yes, being small makes the example easier to read, but if the real world example is as complicated as you make it out to be, then the condition won't be in view while you are editing it anyway. Now, my question is, why would you even think of re-evaluating the condition over and over again when a
    # begin infinite loop
    {
      ...
    } #end infinite loop
    would suffice? Programmers tend to go on and on about how code should be self explanitory and not need comments, but why not toss in a comment and let the darned thing go without a condition? Honestly, when you comment code, it should clear up any questions that might arise, although I believe that this type of thing is not actually confusing, merely it seems to be unfamiliar. I remember the first time I sat down at a terminal and read a B.A.S.I.C. program. Even though it was english-like, it was in no way readable. Then I read another, and a few more, and suddenly GOSUB was not a weird word that some cryptic programmer placed there to confuse me, but a very useful tool. I don't even want to get into the first time I came across a "standard replacement" for the lack of a case statement in perl {scary}. My point is, things that are unfamiliar are not necessarily bad. There are many people who scorn the use of unfamiliar practices, but those tend to be "Seat-Filler", and either refuse to learn new and smoother ways or can't learn at all.

    In the interest of having a style, a few comments placed can be a very satisfactory clarifyer to bring coders with different experience {not more OR less} to the same page as you. As for the actual question about it being "acceptable style", the best person(s) to answer that is your Manager or Peer Review Board. If you don't have those, you may be doing this for yourself, or the code is commented well enough to allow for your personal style {borrowed from merlyn or not} to shine brightly. Go forth and code either way.

    Lord Wrath
      If you have a choice between an idiom and an uncommon construct that will require a comment, go for the idiom. Reading code is a lot more difficult than reading English, and takes a different kind of brainpower. Switching back and forth between the two takes cycles.

      I can skim well-formatted and idiomatic code and get the gist of things. If I haven't written it, sometimes I can even pick out bugs that way. If there's no real need to use something different, you're adding maintenance costs for no real gain. Yeah, comments help, but they have a way of being ignored and of becoming obsolete very quickly.

        True, the switching back and forth between english and code takes a gear shift. So in order to have a happy medium, why not make the comment
        #while(1) psuedo-loop
        {
          ...
        } #end psuedo-loop
        Seems logical. Therefore, you have the readability and the code is not actually re-evaluating the condition.

        I am currently working in a place where it is common and acceptable for everyone to use their own style if formatting. For example, I make my if like this:
        if(condition)
        {
          indented code block
        }
        mostly due to my 2 year job as faculty assistant and grader for my college
        #begin sub-rant
        {as a matter of fact I stopped helping people debug errors in lab if it was not formatted in a similar way since it's so hard to read it}, but I never once criticised a neat way of doing something because it made me think or look at it a second time. I learned many tricks from the students I was supposed to be helping to teach.
        #end sub-rant

        yet many of the people I work with use the format
        if (condition) {
        unindented code block
        }
        and many other variations. It's something I just learn to shift my brain around. I absolutley can't stand to look at these j-builder style formats, but I hafta. So the argument if you don't hafta do something different don't, doesn't hold much with me.

        Besides, the thought of leaving in un-needed processor instructions {for whatever reason, be it not knowing what it is, or just to make my code pretty} starts to remind me of a well documented taboo that is talked about a lot here. I agree that comments are often ignored and so on, but there is a risk we take when trying to standardize a new way of solving things. I'm sure the first case statement was met with people crying how different is was and why don't we just use a lot of if's. Sure, it can be done that way, but maybe the labeled block, or commented block is the right way to have an infinite loop. I mean really, while(1) is probably the most misleading way of doing it, because when is 1 ever != 1, and yet programs all over the globe end in a timely manner. So I say it's all personal choice.

        Lord Wrath
Prefer while() with an occasional redo {was Re: do/redo or for(;;): what's Kosher?}
by dave_aiello (Pilgrim) on Jan 04, 2002 at 02:20 UTC
    I primarily do Perl / SQL integration for a living, so I am partial to the while loop with an occasional redo. A lot of my programs require a "belt and suspenders" approach to data integrity, so I will often test for previous existance of a row or foreign key relationships before doing insert, update, or deletes.

    For example, occasionally, a program will receive data from someplace else and conclude that an update needs to be done. However, the database does not contain the row in question. In that case, we change some aspect of the data so that an insert will take place, and perform a redo. This is by far the most intuitive way to perform a task like this.

    OTOH, I don't like the "eternal-block" construct that you describe very much, unless you label the block and put in extensive comments. Maybe I'm too focused on my typical application, but I don't see very many benefits to doing this.

    Dave Aiello
    Chatham Township Data Corporation

Re: do/redo or for(;;): what's Kosher?
by Juerd (Abbot) on Jan 04, 2002 at 13:30 UTC
    Some say for(;;) is inefficient, because it's a C-style loop.
    Some say {; redo;} is inefficient, because it won't let perl optimize.
    Some say while(1) is inefficient, because the 1 has to be evaluated over and oer.

    I wanted to know, so I benchmarked. I found it somewhat hard to benchmark infinite loops, so I made them finite with a nice closure construct.

    #!/usr/bin/perl -w use strict; use Benchmark; sub ender () { my $i = 0; return sub { ++$i == 10000 } } Benchmark::cmpthese(-5, { 'for (;;)' => sub { my $end = ender(); for (;;) { last if $end->() } }, 'while (1)' => sub { my $end = ender(); while (1) { last if $end->() } }, '{; redo;}' => sub { my $end = ender(); { last if $end->(); redo; } } }); __END__ Benchmark: running for (;;), while (1), {; redo;}, each for at least 5 CPU seconds... for (;;): 9 wsecs (5.48 usr + 0.01 sys = 5.49 CPU) @ 57.56/s (n=316) while (1): 8 wsecs (5.26 usr + 0.01 sys = 5.27 CPU) @ 60.34/s (n=318) {; redo;}: 8 wsecs (5.08 usr + 0.00 sys = 5.08 CPU) @ 57.68/s (n=293) Rate for (;;) {; redo;} while (1) for (;;) 57.6/s -- -0% -5% {; redo;} 57.7/s 0% -- -4% while (1) 60.3/s 5% 5% --


    I've tried this many times. while seems to be the winner.

    Update (200201041112+0100)
    This is really odd. I've run the benchmark above about a hundred times now, and while won almost every one of them.

    BUT
    2;1 juerd@ouranos:~$ perl -MO=Deparse -e'while (1) { print "1\n" }' for (;;) { print "1\n"; } -e syntax OK 2;0 juerd@ouranos:~$ perl -MO=Deparse -e'for (;;) { print "1\n" }' for (;;) { print "1\n"; } -e syntax OK

    Can anyone explain this?

    2;0 juerd@ouranos:~$ perl -e'undef christmas' Segmentation fault 2;139 juerd@ouranos:~$

      The constructs for (;;), while (1), and until (0) all generate exactly the same opcodes. If one is consistently faster than the other, that might perhaps tell you more about your benchmarking procedure than about the code you're trying to benchmark. :-) I tried running your code with the time limit increased from 5 CPU seconds to 60 CPU seconds, and got:
      Rate {; redo;} while (1) for (;;) {; redo;} 17.7/s -- -4% -5% while (1) 18.5/s 4% -- -0% for (;;) 18.5/s 5% 0% --
      suggesting that while and for are the same speed after all, which is something of a relief.
Re: do/redo or for(;;): what's Kosher?
by Stegalex (Chaplain) on Jan 04, 2002 at 01:07 UTC
    I prefer for(;;) because it can be stated in fewer characters thus making for more dense and tasty code. I also like chicken.
Re: do/redo or for(;;): what's Kosher?
by ajdelore (Pilgrim) on Jan 04, 2002 at 05:48 UTC

    Personally, I also prefer the  while (1) construct for an infinite loop. I think that this is because it is commonly used in python, my former language. :) But, it seems to make more sense than a plain block, and looks nicer than an empty for statement.

    I also thing using next or last statements in a while loop is more useful and makes more sense than redo in a plain block.

    Why hasn't anyone suggested using a goto statement yet? After all, TMTOWTDI... lol...

    # Anthony DeLorenzo ajdelore

Re: do/redo or for(;;): what's Kosher?
by toma (Vicar) on Jan 04, 2002 at 05:56 UTC
    For infinite loops I like
    while(1>0) { # do stuff }
    This makes it really obvious that I mean 'forever'. Some of my co-workers have said that it really jumps out at them.

    It should work perfectly the first time! - toma

      Personally, I just omit the condition all together:
      while() { ... }
      Not sure why it works, but it does.
        This is why it works:
        2;0 juerd@ouranos:~$ perl -MO=Deparse -e'while () { print "1\n" }' for (;;) { print "1\n"; } -e syntax OK


        Many people aren't going to like this:
        2;0 juerd@ouranos:~$ perl -MO=Deparse -e'while (1) { print "1\n" }' for (;;) { print "1\n"; } -e syntax OK


        2;0 juerd@ouranos:~$ perl -e'undef christmas' Segmentation fault 2;139 juerd@ouranos:~$

      Along similar lines, I sometimes use
      until (2 + 2 == 5) { ... }
      which evokes eternity beautifully, in my mind.
        Reminds me of a friend of mine (Hendrix fan), who always uses
        
          if (6==9)
        
        as a break condition. Very cool, IMO.
Re: do/redo or for(;;): what's Kosher?
by Ionizor (Pilgrim) on Jan 04, 2002 at 03:39 UTC
    Well, I'm just a first year Engineer but in my program design class we were taught that if you need an infinite loop you should rethink your program design because every non-realtime program should terminate _eventually_.

    Given then, that you still need an infinite loop, I have to ask if you will be collaborating with others on any script where you might use this. If you're just writing scripts to run on your basement webserver, it's not a problem at all but if you plan to work with others, readability, in my opinion, becomes one of the most important factors.

    In the end, I suppose, it just comes down to what you (and anyone you work with) require out of the script - if it's performance, to hell with readability; if it's future tweaking, make it readable instead of fast, etc.

Re: do/redo or for(;;): what's Kosher?
by rje (Deacon) on Jan 19, 2002 at 00:39 UTC
    Apparently, Perl 6 will solve this problem for me and everyone else, with the elegant structure:
    loop { ... }
    rje

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://136026]
Approved by root
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others about the Monastery: (3)
As of 2024-03-29 04:39 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found