in reply to Last undefines a for loop's itererator?

So everyone has explained why this behavior happens, and I've been bitten by this myself. Every time that I code
for $i ( 1 .. $foo ) { last if bar($i); } print $i;
or something like it, I end up having to change that to
for( $i = 1; $i <= $foo; $i++ ) { last if bar($i); } print $i;
I want to still use foreach, but I never can, so I end up using C-style for more and more these days. Is there any good idiom that uses foreach, but yet still lets me access the variables afterwards? (And no, the chunk is far too small for a subroutine.)

Replies are listed 'Best First'.
Re^2: no chunk is too small
by ewilhelm (Novice) on Nov 12, 2005 at 17:49 UTC
    for $i ( 1 .. $foo ) { last if bar($i); } print $i;
    Is there any good idiom that uses foreach, but yet still lets me access the variables afterwards? (And no, the chunk is far too small for a subroutine.)
    use warnings; use strict; my $i = sub { for my $i (1..5) { return($i) if ($i == 4); } }->(); print $i;

    If it doesn't work under warnings and strict, there is probably a good reason for that. IMO, if you want to access the loop variable outside the loop, you should declare a separate variable and set it explicitly before you drop out of the loop.

    If what you really want is a loop that behaves like a subroutine, why not just write it as such?

      If it doesn't work under warnings and strict, there is probably a good reason for that. IMO, if you want to access the loop variable outside the loop, you should declare a separate variable and set it explicitly before you drop out of the loop.
      It does work fine under warnings and strict, if you don't declare your variable in the conditions on the for(..) loop. It has always seemed weird to me to write code like
      my $iold; for my $i ( 1 .. $foo ) { if( bar($i) ) { $iold = $i; last; } }
      Doesn't it just seem like a waste of variables? You're clearly using $i and $iold for the same purpose.

      As for why it isn't a subroutine, perhaps because I like this code all in one place. Shrug.

        It has always seemed weird to me to write code like

          my $iold;
          for my $i ( 1 .. $foo ) {
            if( bar($i) ) {
              $iold = $i;
              last;
            }
          }
        

        Doesn't it just seem like a waste of variables? You're clearly using $i and $iold for the same purpose.

        No, you're using $i as a loop variable and $iold as the return value of the loop. Variables are cheap. Using them to buy clarity is a "good deal"™.

        As for why it isn't a subroutine, perhaps because I like this code all in one place. Shrug.

        This code is all in one place. It is just a loop with a return value. Read carefully.

        my $iold = sub { for my $i ( 1 .. $foo ) { if( bar($i) ) { return($i); } } }->(); # calls this anonymous sub (setting $iold)

        Note that it takes the same number of lines to set an external (to the loop) value and last; as it does to define your loop as an anonymous sub and return($i);

        This also gets you the benefit that if the loop gets more complicated or needs to be reuse, refactoring just means yanking it verbatim and making it a named sub because nothing inside of it relies on lexical scope. (Yeah, you have to put any parameters in the ->($var1, $var2); part for this to scale.)

        I agree. If the variable used as the loop variable is a pre-existing variable and my is not used, it should not be localised and retain is final value after the loop body.


        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.
Re^2: Last undefines a for loop's itererator?
by sauoq (Abbot) on Nov 12, 2005 at 18:50 UTC
    I want to still use foreach

    Why? A C-style for is perfectly acceptable here. That is, so long as you use it correctly. For the example you gave to match your foreach loop, you'd have to change your condition to $i < $foo because your current code, $i <= $foo, will leave you with $i being one more than $foo if you get to the end of your loop.

    -sauoq
    "My two cents aren't worth a dime.";
    
      I disagree that my loops aren't equivalent. It is common practice in C to write loops of the form
      for( $i = 0; $i <= $foo; $i++ ) { last if bar( $foo ); } if( $i > $foo ) { # Condition never met! }
      Essentially, what I would like to do is distinguish between bar(..) being true for the last element in my last, and never being true at all. In "my perfect little world", I would like foreach to unset its iterator variable if and only if it loops through the entire list without encountering last.

      So, in my example above, if bar( $foo ) is the first to be true, I want $i == $foo; if bar(..) was never true, it'd be nice to have $i be undef.

      In addition, if bar(..) has side effects, then our code is not equivalent.

      Edit: Also, I prefer a foreach-style loop because all I'm doing is iterating -- precisely the designated purpose of foreach. Otherwise, if I have a long variable name, I would have to type for( $long_variable_name = 1; $long_variable_name <= 5; $long_variable_name ++ ) versus for $long_variable_name ( 1 .. 5 ). I'm pretty sure I'm not the only one who has made copy-paste errors like for( $j = 0; $j <= 5; $i++ ).

        I would like foreach to unset its iterator variable if and only if it loops through the entire list without encountering last.

        It doesn’t unset the iterator, it restores it. But people already get bitten by retained values of $1 and friends when a pattern with capturing parens failed to match. Do you really want to perpetuate this pattern into such a common construct as foreach?

        If you want to retain the value, save it. It’s not hard, it makes it explicit what’s going on, and it lets foreach follow consistent mechanics with fewer surprises.

        Makeshifts last the longest.

Re^2: Last undefines a for loop's itererator?
by BigLug (Chaplain) on Nov 12, 2005 at 21:03 UTC
    Rather than using $i as your iterator, use the default $_. Then assign it's value to your 'custom iterator' inside the foreach.

    foreach ( 1 .. $foo ) { $i = $_; last if bar($i); } print $i;
      Thank you very much. Of all the solutions, I like this the best.
Re^2: Last undefines a for loop's itererator?
by Aristotle (Chancellor) on Nov 12, 2005 at 21:51 UTC

    The problem with iterating over indices is that BUU is iterating over a sorted temporary list, so if you don’t create it en passant in the foreach() list, you need to store it in a temporary array variable. And since you have a temp array, you can afford to destroy it, so the simplest approach is while( @list ) { ...; shift @list }.

    Makeshifts last the longest.