Perl Breaking out of an If statement

2020-05-24 20:50发布

问题:

This one just came up: How do I break out of an if statement? I have a long if statement, but there is one situation where I can break out of it early on.

In a loop I can do this:

while (something ) {
    last if $some_condition;
    blah, blah, blah
    ...
}

However, can I do the same with an if statement?

if ( some_condition ) {
    blah, blah, blah
    last if $some_other_condition; # No need to continue...
    blah, blah, blah
    ...
}

I know I could put the if statement inside a block, and then I can break out of the block:

{
    if ( some_condition ) {
        ...
        last if $some_other_condition; # No need to continue...
        blah, blah, blah
        ...
    }
}

Or, I can create a subroutine (which is probably better programmatically):

if ( some_condition ) {
    run_subroutine();
}

sub run_subroutine {
    blah, blah, blah
    return if $some_other_condition;
    blah, blah, blah
    ...
}

But is there any way to exit an if condition?


Resolution

The question came up because I was helping someone with their code. Inside a fairly long if statement, there were several other if statements embedded in it. The code looked something like this:

 if ( $condition1 ) {
    blah, blah, blah;
    if ( not $condition2 ) {
       blah, blah, blah;
       if ( not $condition3 ) {
          blah, blah, blah;
       }
    }
}

I thought the whole thing could be made more readable by doing this:

if ( $condition1 ) {
    last if $condition2;
    blah, blah, blah;
    last if $condition3;
    blah, blah, blah;
}

This shows that the normal flow of the if statement is standard, but under certain conditions, the if statement was exited early -- much like using last or next in a while or for loop to exit the loop.

I liked mpapec's solution of using a label -- even if I don't use the label itself. The label is a description of my if:

IF-UNDER-CONDITION1:
{
    if ( $condition1 ) {
        last if $condition2;
        blah, blah, blah;
        last if $condition3;
        blah, blah, blah;
    }
}

Although it isn't a standard coding technique, the flow of the code is obvious enough that a typical low-level Perl developer (the one that has to maintain this code after I leave) could figure out what the code is doing and maintain it. They may even learn something in the process.

回答1:

You can use basic block which is subject to last, next and redo, so there is possible break from it.

if ($condition) {EXIT_IF:{

   last EXIT_IF; # break from code block

   print "never get's executed\n";
}}

EXIT_IF: {
  if ($condition) {

     last EXIT_IF; # break from code block

     print "never get's executed\n";
  }
}


回答2:

  • Put it inside an empty for() loop, and add last; everywhere you want to break out AND after the if. A bit ugly but works. Make sure to add comments to explain the trick.

    for (;;) {
        if (condition) { 
            #code
            last if another_condition;
        }
        last;
    }
    
  • use goto and label a statement after your loop for that goto. Be forever damned.

  • Extra block inside the if (e.g. if () {{ code }}). May be hard to read for novices but OK if accompanied by a comment.

  • your own solution: block around if. Not very obvious readability-wise.

  • your own solution: subroutine with return.

    Frankly, unless the cost of calling a sub matters performane wise, this is the cleanest solution as far as readability.



回答3:

You could put the rest of your if block inside another if statement, like this:

if (some_condition) {
    blah, blah, blah
    if (!$some_other_condition) {
        blah, blah, blah
        ...
    }
}


回答4:

Another alternative is to use an anonymous subroutine.
Note: I don't recommend this method because of the added scoping complexity (see note below); it is just for completeness of possible answers.

if( $true_condition ){
   (sub{
       return if $true_condition;
       ...
   })->();
}

Note: any variables declared w/in the routine must use our instead of my if you wish to use them in the rest of the code.



回答5:

I tend to use sequential if-statements based on a "do I continue?" variable instead. Your

if ( $condition1 ) {
  blah, blah, blah;
  if ( not $condition2 ) {
     blah, blah, blah;
     if ( not $condition3 ) {
        blah, blah, blah;
     }
  }
}

can be rearranged to

my $ok = $condition1;
if ($ok) {
  blah, blah, blah;
  $ok = not $condition2;
}
if ($ok) {
  blah, blah, blah;
  $ok = not $condition3;
}
if ($ok) {
  blah, blah, blah;
}


回答6:

Keep your while loop so you can use last but also make sure that the loop is executed at most once

my $loop_once = 1;
while ( $loop_once-- and some_condition ) {
    blah, blah, blah
    last if $some_other_condition; # No need to continue...
    blah, blah, blah
    ...
}


回答7:

I was inspired by DVK's answer to play around, and I came up with this variant that works at least on Perl 5.26.1:

for( ; some_condition ; last ) {
    blah, blah, blah
    last if $some_other_condition; # No need to continue...
    blah, blah, blah    
}

Per perlsyn, this is equivalent to:

while (some_condition) {
    blah, blah, blah
    last if $some_other_condition; # No need to continue...
    blah, blah, blah    
} continue {
    last;
}

In a continue block, last has the same effect as if it had been executed in the main loop. Therefore, the loop will execute zero or one times, depending on some_condition.

Tests

perl -E 'my ($cond, $other)=(X, Y); 
         for(;$cond;last) { say "hello"; last if $other; say "goodbye" }'

has the following results, for various X and Y values:

X Y   Prints
-----------------------
0 0   (nothing)
0 1   (nothing)
1 0   hello, goodbye
1 1   hello