in reply to Re: Re Execute Lines of Code (redo)
in thread Re Execute Lines of Code

This node falls below the community's minimum standard of quality and will not be displayed.

Replies are listed 'Best First'.
Re^3: Re Execute Lines of Code (redo "bad practice"?)
by shmem (Chancellor) on Jan 27, 2008 at 16:10 UTC
    redo is actually a kind of goto and is bad practice
    Huh? So are next and last. "Bad practice" also? What convoluted constructs would you suggest instead of these?

    Update: "bad practice" implies that there's "better practice" which supersedes the "bad practice". What would that be for redo? (or next, or last?)

    --shmem

    _($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                                  /\_¯/(q    /
    ----------------------------  \__(m.====·.(_("always off the crowd"))."·
    ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
Re^3: Re Execute Lines of Code (redo)
by dsheroh (Monsignor) on Jan 27, 2008 at 18:19 UTC
    The major objection I'm aware of to goto is that it lets you basically jump from anywhere to anywhere else without any real warning at the destination that this could happen or under what circumstances. redo does not share this characteristic, as it is a loop control statement, the same as next.

    What then is the reason for your specific (i.e., not simply "it reminds me of goto") objection to redo?

    A reply falls below the community's threshold of quality. You may see it by logging in.
Re^3: Re Execute Lines of Code (redo)
by runrig (Abbot) on Jan 27, 2008 at 23:17 UTC
    are you sure its not redo LOOP you mean ?

    It's not necessary, but if I went through the trouble if labeling the loop (as ikegami did), then I'd also include it for clarity in any loop control statements.

    redo is actually a kind of goto and is bad practice.

    Phfft. If/then/else is a kind of goto...if you want to look at it that way.

Re^3: Re Execute Lines of Code ("loops")
by tye (Sage) on Jan 28, 2008 at 04:25 UTC

    First, I try to limit uses of next, last, and redo because they tend to be hidden in the middle of a loop (making the loop control conditions less than obvious) and they transfer control to places that can be less than obvious (it can take some real visual searching to figure out which surround block(s) are loops, often having to check both top and bottom of each successive surrounding block).

    I prefer to only use them in quite simple loops (and to find them only used in such). So, for example, I make the complex loop control more obvious by simplifying the loop by moving most of the non-control complexity into subroutines. It is often even better to replace last with the use of early return from a subroutine. Since last is by far the most-used of these constructs, early return is often enough to completely eliminate them from your code.

    Second, and more importantly, I don't consider the use of a bare block as a "loop" to be a wise choice. This is what is very much like using goto, even worse in some respects.

    A bare block is quite unlike a loop. The only thing that makes it even slightly like a loop is that these loop control keywords (next, last, and redo) do something in them. But they don't really work like loop control in these cases, they work exactly like goto. Let us only consider last and redo in bare blocks since, in a bare block, next does the same thing as last and would be a more confusing choice.

    A bare block w/o last nor redo doesn't loop at all (and is a fine construct if used for reducing the scope of some lexical variables or pragmas). A bare block with (only) a redo is exactly like a goto to the opening brace. The closing brace is completely pointless to the so-called "loop". So compare:

    { # body redo if ...; # tail } # post # vs TOP: # body goto TOP if ...; # tail # post

    The latter is actually a lot clearer. It is easier to see that the top of the "loop" might have control transfered to it. It is easier to determine where the goto is sending control than to where the redo is going. There is no obfuscatory splitting of the "tail" and "post" parts of the code. Note that if there is no "tail" portion, then the following improvement is possible:

    { # body redo if ...; } # post # becomes: do { # body } until( ! ... ); # post

    A bare block with (only) a last is exactly like a goto to the closing brace. The opening brace is completely pointless to the so-called "loop". So compare:

    # bare block "loop": { # head last if ...; # body } # post # vs goto: # top? # head goto END if ...; # body END: # post # vs if: # head if( ! ... ) { # body } # post

    The clearest choice there seems obvious to me.

    Now consider a bare block with both redo and last. It is exactly like two labels with gotos except that it is often less clear. Is isn't much like a loop:

    { # top last if ...; # middle redo if ...; # tail } # post TOP: # top goto END if ...; # middle goto TOP if ...; # tail END: # post

    The former can easily become quite obfuscated. The latter is at least clear in the flow control (the labels being obvious to find and to match up). The only advantage I see to the former is that it doesn't require one to come up with unique label names, but if that becomes a problem then I suspect the code has become quite difficult to follow indeed.

    If I find myself creating code that flows like that, then I don't use a bare block as a "loop" and kid myself that my code is clear. I instead realize that I'm using goto-like flow and I need to rethink a bit and probably refactor. The solution is usually to create a small subroutine (or two) that can be used in a conditional for a "real", obvious loop and/or a small subroutine that does an early return. Sometimes not even that is required.

    A big problem with goto, IMHO, is that it is too easy to use it to achieve your first idea of how to do flow control, not encouraging you to come up with a simpler or clearer expression of the required flow. In (tye)Re: Closures as Itterators I show an easy solution to something that several people were solving in complex ways using bare-block "loops". By sticking to "real", obvious loops, I forced myself to rethink how the code is flowing and produced clearer code.

    In (tye)Re: crafting a decent CLI, I talk more about this stuff as well.

    In the original use of bare-block plus redo, the sample code is so small that the potential for lack of clarity is not very well demonstrated. But it is easy to replace the code with something that doesn't contain such an easy-to-abuse and easy-to-become-hard-to-understand construct:

    LOOP: { try something; if (!success) { wait; redo; } } # becomes: while( 1 ) { try something; last if failed; sleep $seconds; }

    But even better is to factor out what is being tried so your code becomes even clearer:

    while( ! try_something() ) { sleep $seconds; }

    - tye        

      Now consider a bare block with both redo and last. It is exactly like two labels with gotos except that it is often less clear.

      Then why not use loop labels for redo, next and last?

      FILE: while ( my $file = $DIR> ) { ... last FILE if ( condition1 ); ... redo FILE if ( condition2 ); }

        After thinking about that for a while, I'm coming to like the idea. It clues in the reader that some unusual flow is going to happen with this loop so they can't assume the loop conditional is the end of the story. And it makes the unusual, mid-loop flow changes easier to spot.

        I still prefer to just avoid such. But next time I find the desire to use one, I'll consider that option.

        - tye        

      The latter is actually a lot clearer. It is easier to see that the top of the "loop" might have control transfered to it. It is easier to determine where the goto is sending control than to where the redo is going

      But that's only because you used a label in the goto code. That's why I used a label in my redo code even though it wasn't required. I must admit I forgot to place the place the label on the actual redo like I normally do, but I meant to. I use redo just like I would use goto. The only difference is that the word redo conveys the reason for which I am jumping.

      By the way, you inverted the condition in your second last snippet. It should read last if success; to be equivalent.

      I agree with the view that next, last, and redo can make code less readable.

      A bare block is, in Perl, a loop that falls through after its first pass. This is by language definition and so it should not be discounted in one's mental model of the language.

      I think of a bare block primarily as a looping construct. If a block follows the normal pattern of a closure, I expect it to be just a scoping construct. I haven't found the worth of a bare block to put over or under a package:

      { package Yme; code; }
      and
      package Ynot; { code; }

      I am not enamoured of labels in Perl and tend to avoid them. Labels should be used only when necessary to choose a loop or to clarify a mess which cannot be cleaned up.

      Labels smell. They all fall into one namespace, cannot be declared, and are slimey to name. Labels and goto behavior seem inadequately defined in the docs. Many people expect a goto LABEL to go to the textually nearest label, with more active use, this model breaks down because the actual behaviour is more dynamic.

      Pop quiz:

      In the below code, what is the output?

      And again, with the marked lines uncommented?

      sub lup { "LOOP" } sub say { print @_, $/; sleep 1; } LOOP: say 0; #sub mysub { # uncomment INNER: { say 1; goto +lup(); say 2; } LOOP: { say 3; goto LOOP; LOOP: say 2; } #} ### uncomment #mysub(); ### uncomment

      Be well,
      rir

        A bare block is, in Perl, a loop that falls through after its first pass.

        It is a loop that doesn't loop. If you prefer, you can update my statements to be that when I see a bare block, I don't expect it to loop. The vast majority of the time I am correct (or, all of the time I am correct since what next, last, and redo do inside a bare block is a bit of a stretch to call looping anyway -- redo comes close but it doesn't make the whole "loop" loop so even through there is some looping going on there, it is going on in a subset of the block so it isn't the block that is looping).

        This is by language definition and so it should not be discounted in one's mental model of the language.

        If I see hoof prints, I don't expect that they are from a zebra. That doesn't mean that I'm not aware of zebras nor that I discount the possiblity of zebras especially when I see black and white stripes, especially when I'm at a zoo. But I certainly discount zebras when walking around away from the zoo and away from Africa, even when I see hoof prints. And I feel completely justified in doing so. When I see a lone open brace, I absolutely assume that it isn't going to loop rather then jumping to look for things that would make it (not-quite) loop nor even wondering whether or not it was meant as the start of some (not-quite) "loop". Revising that assumption in the rare cases is less work in the aggregate.

        By the language definition, do { ... } while( ... ); is not a loop, it is just a non-loop that loops. These two unfortunate language definitions have been corrected in the next design. So I am in good company in finding fault with them.

        I'm not even convinced that the "language" defines loops that way. Surely the parts that deal with next, last, and redo define loops that way. They also define ... while ...; as a non-loop in that regard. But I doubt you are told to narrow the scope of a lexical variable by enclosing it in a loop.

        But even if the language defines "loops" as things in which next, last, and redo work, I find that a pretty poor choice of terminology and will stick to defining "loops" as things that "loop". This leaves me without a term for "things in which next, last, and redo work". I'm not bothered as '"loop"' (the word in scare quotes) works for me.

        As to labels, I've never had much use for them at all and certainly don't encourage using them prolifically. The proposed use of labels sounds intriguing but I haven't put it to use, much less used it enough to have a confident proclamation as to how good of an idea it is in practice.

        But none of your valid complaints about labels actually apply to the proposed use of labels. You could use such horrid lable choices as "LOOP" or "LABEL" and never change the behavior of the code by adding the "clarifying" labels. If you label every loop that contains a next/last/redo with the exact same "LOOP:" label, and change every next; to next LOOP;, Perl will find the correct label (but the human reading the code might not). So even the slightest reduction of ambiguity offered by labels "LOOP1:" and "LOOP2:" would improve clarity.

        But there is the risk of mistyping a label. If you mistype a label and happen to match a label used in a loop from the current dynamic scope, then you'll likely only get a warning to help you realize your mistake. Well, unless you have unit tests which should excercise the code w/o surrounding loops and detect the error for you quite early.

        So I encourage a healthy suspicion of labels. If you make the use of next/last/redo quite rare, then the addition of labels to such uses might be a good idea. I can see value in such a practice and still have not seen much to discourage such a practice. Note that I'd use labels more like "FILE", "LINE", "TOKEN", whatever the loop is iterating over. I'd never use "LOOP" any more than I'd use "LABEL".

        - tye        

        Your pop quiz could just have a bare block where you have sub mysub { ... } (and no mysub() call).

        { print "endlessly\n" } continue { sleep 1 and redo }

Re^3: Re Execute Lines of Code (redo)
by ikegami (Patriarch) on Jan 28, 2008 at 00:59 UTC

    My first version used last, which is no different from redo. I don't see how you can say one's better than the other.

    Furthermore, I don't agree that using them is bad practice. next, last and redo break idealistic control flow, but it turns out they make the code significantly clearer when used properly.

Re^3: Re Execute Lines of Code (redo)
by blazar (Canon) on Jan 29, 2008 at 09:50 UTC

    I personally believe you're completely misunderstanding: generic goto is a bad practice because it lets you unconditionally jump from a random exectution point to another. It's this particular point that's stressed in the very seminal article you linked to. (And we don't need you to point us to such a notorious resource.) OTOH next, last and redo are not simply "a kind of goto", but they are especially tamed kinds of goto which are not associated with the same wild behaviour. tye has some good points, but you're trivializing the subject matter.

    --
    If you can't understand the incipit, then please check the IPB Campaign.
      NO.
      You don't understand.
      Please re-read the article.
      Thank you.
A reply falls below the community's threshold of quality. You may see it by logging in.