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

Does the last keyword only care about braces when determining what constitutes a loop? I have the following code:

#!/usr/bin/perl -w use strict; my $last_test_file = "./last_test_file"; printf("before loop\n"); while (1) { if (open TEST_FILE, $last_test_file) { printf("after open\n"); /^\s*TEST_PATTERN\s+/ and last while <TEST_FILE>; printf("after match\n"); close(TEST_FILE); } else { die("ERROR: Unable to open $last_test_file for read, exiting.. +.\n"); } } printf("after loop\n");

Assume last_test_file contains the text TEST_PATTERN on line 6, and non-matching text on all other lines. My expectation is that the above script will open last_test_file, read the first 5 lines without matching, then read line 6, match, exit the inner while loop due to the 'last' statement, print "after match", close the file handle, and repeat this process indefinitely due to being stuck in the while(1) loop. What happens in actuality is the script never makes it to the "after match" text; the 'last' statement appears to be breaking out of the outer while (1) loop rather than the inner while <TEST_FILE> loop. If I replace the single-line while loop above with the following:

while (<TEST_FILE>) { /^\s*TEST_PATTERN\s+/ and last; }

The script behaves as I described above, i.e. it remains stuck inside the while (1) loop and the 'last' statement only breaks out of the while (<TEST_FILE>) loop. This occurs with perl 5.26.1.
Here are my questions:

1. Does 'last' use the presence of braces to determine what constitutes a loop? Does 'last' officially not work with single-line while loops like the above? I was not able to determine this from the perldoc page for 'last' (http://perldoc.perl.org/functions/last.html).

2. Are single-line while loops like the one above considered bad programming practice in general?

3. I am running with "use strict" and warnings enabled. Shouldn't my single-line while loop with 'last' be flagged at least as a warning, given that the last statement doesn't apply to the loop it is used within? Is there any higher level of warning that can be enabled to catch things like this?

Replies are listed 'Best First'.
Re: Use of 'last' within single-line while loops
by ikegami (Patriarch) on Mar 21, 2018 at 00:59 UTC

    last will exit the inner-most loop statement. Those are:

    • BLOCK
    • while (EXPR) BLOCK (or until)
    • for (LIST) BLOCK (or foreach)
    • for (EXPR..EXPR) BLOCK (or foreach)
    • for (EXPR; EXPR; EXPR) BLOCK (or foreach)

    Expressions with loop statement modifiers are not considered by last. The same goes for loop-like functions. These include the following:

    • EXPR while EXPR; (or until): Expression with statement modifier. (Not a loop statement.)
    • EXPR for LIST; (or foreach): Expression with statement modifier. (Not a loop statement.) last actually does see this as a loop to end.
    • grep BLOCK LIST: Not a statement.
    • map BLOCK LIST: Not a statement.

    All loop statements have braces, but not all braces are related to loop statements. The following are examples that aren't:

    • if (EXPR) BLOCK (or unless): Not a loop.
    • grep BLOCK LIST: Not a statement.
    • map BLOCK LIST: Not a statement.
    • do BLOCK: Not a loop or a statement.
    • eval BLOCK: Not a loop or a statement.
    • sub BLOCK: Not a loop or a statement.
    • sub NAME [...] BLOCK: Not a loop.

    If you want a warning for the code you used, take a look at perlcritic. If it doesn't yet have a rule for it, you could create one.

      > Expressions with loop statement modifiers are not considered by last.

      choroba@still ~ $ perl -wE 'L: for my $x (qw(a b c)) { say("$x$_"), la +st for 1,2,3}' a1 b1 c1 choroba@still ~ $ perl -wE 'L: for my $x (qw(a b c)) { say("$x$_"), la +st L for 1,2,3}' a1

      ($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,

        wut. Well that's not right. Of course, it can't be fixed now without breaking code.

      Correction, works fine with for statement modifier

      say $_ and last for 1,2,3

Re: Use of 'last' within single-line while loops
by AnomalousMonk (Archbishop) on Mar 21, 2018 at 00:57 UTC
    Does 'last' officially not work with single-line while loops like the above?

    From Statement Modifiers in perlsyn:

    Note also that the loop control statements described later will *NOT* work in this construct, because modifiers don't take loop labels. Sorry. You can always put another block inside of it (for "next") or around it (for "last") to do that sort of thing.   [examples follow]

    Are single-line while loops like the one above considered bad programming practice in general?

    Not by me. "Statement modifiers" can, in general, be quite useful IMHO.

    Update:

    Shouldn't my single-line while loop with 'last' be flagged at least as a warning, given that the last statement doesn't apply to the loop it is used within?
    Again IMHO, last is clearly block-oriented and should not elicit a warning.
    Is there any higher level of warning that can be enabled to catch things like this?
    Not to my knowledge. Maybe see perltidy.


    Give a man a fish:  <%-{-{-{-<

Re: Use of 'last' within single-line while loops
by hdb (Monsignor) on Mar 21, 2018 at 08:37 UTC

    In my humble opinion I would suggest the use of labels with last and next in the case of nested loops to make sure the reader of the code (if not the Perl interpreter) is not confused about the intended logic.

    use strict; use warnings; OUTER: while( 1 ) { last OUTER while (1); print "Done with INNER.\n" } print "Done with OUTER.\n"