I have a problem with breaking out of the innermost enclosing loop in doubly nested while loops.
Consider:
use v5.14;
my $i=0;
while ($i<=1) {
my $j=0;
do {
last if $j==2;
say $j++;
} while ($j<4);
$i++;
}
The output is here:
0
1
whereas the expected output should be:
0
1
0
1
Adding a label to the innermost enclosing loop:
my $i=0;
while ($i<=1) {
my $j=0;
LINE: do {
last LINE if $j==2;
say $j++;
} while ($j<4);
$i++;
}
gives error:
Label not found for "last LINE" at ./p.pl line 9.
You can only use last, next and redo in loops that use a BLOCK.
while (EXPR) BLOCKuntil (EXPR) BLOCKfor (LIST) BLOCKfor (EXPR; EXPR; EXPR) BLOCKBLOCKYou can't use them on loops created by statement modifiers[1].
EXPR while EXPR;EXPR until EXPR;do BLOCK while EXPR; is just a case of EXPR while EXPR;. The loop doesn't have a BLOCK for last to work on. It's merely a while statement modifier that's modifying do function[2]. The do function being modified has a BLOCK, but do is not a loop.
You could add a loop to last out of, such as a bare loop (BLOCK) that's normally executed only once.
my $i=0;
while ($i<=1) {
LOOP: {
my $j=0;
do {
last if $j==2;
say $j++;
} while ($j<4);
}
++$i;
}
But if we could make sure ++$i always got executed, we could just next the outer loop.
my $i=0;
while ($i<=1) {
my $j=0;
do {
next if $j==2;
say $j++;
} while ($j<4);
} continue {
++$i;
}
The above simplifies to
for (my $i=0; $i<=1; ++$i) {
my $j=0;
do {
next if $j==2;
say $j++;
} while ($j<4);
}
which simplifies to
for my $i (0..1) {
my $j=0;
do {
next if $j==2;
say $j++;
} while ($j<4);
}
Of course, that assumes we need to preserve the do while. If not, we can just write
for my $i (0..1) {
for my $j (0..3) {
last if $j==2;
say $j;
}
}
Or so I thought. Turns out it works for for!!!!
>perl -we"print($_), (($_ == 5) && last) for 1..10; print(qq{\ndone\n});"
12345
done
Well, not quite "merely". It's special-cased to be bottom-tested instead of top-tested.
As perldoc do and everyone has already stated:
do BLOCKNot really a function. Returns the value of the last command in the sequence of commands indicated by BLOCK. When modified by the
whileoruntilloop modifier, executes the BLOCK once before testing the loop condition. (On other statements the loop modifiers test the conditional first.)do BLOCK does not count as a loop, so the loop control statements
next,last, orredocannot be used to leave or restart the block. Seeperlsynfor alternative strategies.
The deeper tribal knowledge with regard to this is that you should reserve these statement modifiers for single line or at most two line statements.
If you want a multiline block to be a loop, the coder should be able to observe that from the very first line. Therefore, instead of ever using a statement modifier with a do BLOCK, setup an infinite loop and create your test to break out of it at the end:
while (1) {
...
last if EXPR;
}
This would change your somewhat contrived example to the following:
use v5.14;
my $i = 0;
while ($i <= 1) {
my $j = 0;
while (1) {
last if $j == 2;
say $j++;
last if $j >= 4;
}
$i++;
}
You can't use last inside do{} block, but you can
my $i=0;
while ($i<=1) {
my $j=0;
LINE: {
do {
last LINE if $j==2;
say $j++;
} while ($j<4);
}
$i++;
}
or
my $i=0;
OUTER: while ($i<=1) {
my $j=0;
LINE: do {
next OUTER if $j==2;
say $j++;
} while ($j<4);
}
continue {
$i++;
}
If you really must:
my $i=0;
while ($i++<=1) {
my $j=0;
LINE: {
do {
last LINE if $j==2;
say $j++;
} while ($j<4);
}
}
As noted by others, you can't use last with do:
do BLOCK does not count as a loop, so the loop control statements next, last, or redo cannot be used to leave or restart the block.
Another solution is to use another while loop:
my $i = 0;
while ($i <= 1) {
my $j = 0;
while($j < 4) {
last if $j == 2;
say $j++;
}
$i++;
}
or, with for loops:
for my $i (0 .. 1) {
for my $j (0 .. 3) {
last if $j == 2;
say $j;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With