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) BLOCK
until (EXPR) BLOCK
for (LIST) BLOCK
for (EXPR; EXPR; EXPR) BLOCK
BLOCK
You 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 BLOCK
Not really a function. Returns the value of the last command in the sequence of commands indicated by BLOCK. When modified by the
while
oruntil
loop 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
, orredo
cannot be used to leave or restart the block. Seeperlsyn
for 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