http://www.perlmonks.org?node_id=992070

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

Can I tell Perl to do something when a while loop is finished on it's own accord rather than finished because it has been told to finish with a last LINE if (); like statement?

Replies are listed 'Best First'.
Re: while loop question
by RichardK (Parson) on Sep 06, 2012 at 12:13 UTC

    You can always wrap your while loop in a simple block with a label and use last LABEL

    So something like this :-

    use v5.14; use warnings; my @l = (0..5); sub test { my ($match) = @_; LOOP: { for my $v (@l) { if ($v == $match) { say "found"; last LOOP; } } # fell off the bottom say "not found"; } } test(4); # found test(7); # not found
      Nice ideas. The trouble is, as well as wanting to be able to say last LINE (or LOOP) I want to be able to say  next LINE if $1 < $start_point; and your solution doesn't offer that possibility.

        Of course it does :). It works in just the same way as using next/last in any nested loops.

        OUTER: { INNER: while(1) { next INNER if this(); last OUTER if that(); } }
Re: while loop question
by VinsWorldcom (Prior) on Sep 06, 2012 at 12:13 UTC

    Are you looking for something more elegant than the following ugly code that would do the trick?

    my $LastFlag = 0; while (CONDITION) { ... if (END_CONDITION) { $LastFlag = 1; last; } } if (not $LastFlag) { ... }
Re: while loop question
by Ovid (Cardinal) on Sep 06, 2012 at 13:58 UTC

    Yes, but it has to be handled manually and you'll have to consider the best way of handling that. One way of handling this is retesting the condition in a continue block:

    while (EXPR) BLOCK continue BLOCK

    Note variables defined in the EXPR of the while loop are visible in the scope of the continue block. That's what makes this work even if you can't necessarily do this after the while loop because the condition you want to test may no longer be valid and you can't tell if a last was used or not.

    use 5.10.0; my @list = qw(one two three); while ( defined( my $element = shift @list ) ) { } continue { if ( defined $element ) { say $element; } if ( !@list ) { say "We've now finished processing the while loop"; } }

    And that prints out:

    one two three We've now finished processing the while loop

    Again, the variants of this technique change from time to time, depending on what you need. Be careful, though. Using last in the while loop will skip the continue statement, but that appears to be what you want in this case.

      Within this context  last does not does not skip the  continue block.
      LINE: while (<LINE_INPUT>){ print "I am here B\n"; chomp; my $entry_no_old = $entry_no_new; # a bit of a work around use +d here if ($_ =~ /^(\d{1,3})\t(\d{3,5})/){ next LINE if $1 < $start_point; print "I am here C\n"; $entry_no_new = $1; last LINE if ($entry_no_new > $entry_no_old);
Re: while loop question
by sundialsvc4 (Abbot) on Sep 06, 2012 at 13:54 UTC

    Of all of the possible “solutions” posted here, only VinsWorldcom’s strategy would ever pass muster in my shop.   Why?   Because it works (of course), and because it is instantly and abundantly clear and easily maintainable.   Provided that it can be clearly shown that the final value of my $lastFlag (a sensibly-named variable) at the if-statement can only be determined by the outcome of the while loop that immediately precedes it (which loop cannot be bypassed), this manner of clear software prose makes no pretenses about how it works and leaves no question that it does.   Furthermore, the logic can easily be extended to allow for any sort of tests and any number of tests to be performed within the loop and used to exit from the same.

    Some Monks further advocate the use of last label_name syntax, noting that, particularly in complex logic which might involve multiple nested structures, the contextual meaning of a last statement sans target-label could become broken by a subsequent change that wrapped the statement in another loop.   This is reasonable.

Re: while loop question
by aaron_baugher (Curate) on Sep 06, 2012 at 15:40 UTC

    I know we're supposed to hate goto and never ever ever use it, but it does make for a very clear solution in this case:

    #!/usr/bin/env perl use Modern::Perl; while(<DATA>){ goto SKIPPED if /match/; } say 'Do something after normal loop exit'; SKIPPED: say 'Proceeding with rest of program'; __DATA__ line 1 line 2 match line 3 line 4

    Aaron B.
    Available for small or large Perl jobs; see my home node.

Re: while loop question
by hbm (Hermit) on Sep 06, 2012 at 13:34 UTC

    Another possibility:

    my $match = 9; my @loop = (0..5); for my $v (@loop,undef) { if (!defined $v) { say "not found"; } elsif ($v == $match) { say "found"; last; } }
Re: while loop question
by Rudolf (Pilgrim) on Sep 06, 2012 at 13:32 UTC

    would the continue statement still execute after the loop has ended the last time?

    while(x){}continue{};
      Does continue behave differently in the two circumstances that I have mentioned?
        Yes. continue {} block will not be executed if last is used.
Re: while loop question
by polymorpheus (Novice) on Sep 06, 2012 at 14:06 UTC
    What about using eval { ... } around the loop and die instead of last?
    eval { while(CONDITION) { die if SOMETHING; die if SOMETHING_ELSE; ... } }; if(not $EVAL_ERROR) { # loop finished on it's own accord }
    About as quickly as I thought of this, I remembered a best practice that says something like "do not use exceptions for control flow". I was about to retract my suggesttion, but figured I would ask the other monks. Is the consensus among the monks that using eval/die like this is bad? I suppose it depends on the true nature of the SOMETHING and SOMETHING_ELSE cases.

      I can think of two reasons to not use die/eval for (normal) control flow:

      (1) Clarity. An exception is supposed to indicate — well, an exceptional condition: either an error, or a resource failure. Throwing an exception as a part of normal execution is liable to mislead maintainers of the code when they try to understand what’s going on. So, at the very least, it’s poor style.

      (2) Efficiency. Consider the following code:

      #! perl use strict; use warnings; use Benchmark qw(cmpthese); cmpthese(1_000_000, { 'bare_loop' => \&bare_loop, 'eval_loop' => \&eval_loop, }); sub bare_loop { my ($count, $evens, $flag) = (100, 0, 0); while ($count--) { next if $count % 2; # odd if ($count == 50) { $flag = 1; last; } ++$evens; } print "Normal loop exit\n" unless $flag; } sub eval_loop { my ($count, $evens) = (100, 0); eval { while ($count--) { next if $count % 2; # odd die if $count == 50; ++$evens; } }; print "Normal loop exit\n" unless $@; }

      Typical output (on my machine):

      Rate eval_loop bare_loop eval_loop 71541/s -- -23% bare_loop 92362/s 29% --

      So, there is a definite performance penalty for throwing and then catching the exception. Not nearly as great a penalty as in a language like C++, but still — why opt for a less efficient method when the more efficient methods are just as easy to use?

      Athanasius <°(((><contra mundum

Re: while loop question
by GrandFather (Saint) on Sep 07, 2012 at 00:53 UTC

    You seem to be asking for a fairly specific answer to a very general question. The context for the code could make a great deal of difference to what constitutes a good solution.

    For example, if the while loop is in a sub and you simply want to bail when an error is encountered you could return or die within the loop. That sends a very clear message that an unusual condition is being handled. You could use eval for a block you can return from to achieve the same effect without using a sub. In some situations that may be a clean concise solution. Consider:

    #!/usr/bin/perl use strict; use warnings; my @items = qw(ok fine great bail); eval { while (@items) { return if $items[0] eq 'bail'; print shift(@items), "\n"; } print "No bail items found today\n"; return 1; } or print "Bailed early\n";

    Prints:

    ok fine great Bailed early
    True laziness is hard work
Re: while loop question
by Neighbour (Friar) on Sep 06, 2012 at 11:44 UTC
    Yes, you can, but you'll have to do it yourself.
Re: while loop question
by philiprbrenan (Monk) on Sep 07, 2012 at 16:46 UTC

    Please consider putting the loop in a sub, thus:

    sub foo() {for(1..10) {return 0 if ...; } 1 } if (foo()) {...}

    and giving the sub a meaningful name