29

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.

Community
  • 1
  • 1
David W.
  • 105,218
  • 39
  • 216
  • 337
  • Create a "block" inside the if statement and break out of that. – Floris Velleman Jun 11 '13 at 20:58
  • `goto` a label at the end. Otherwise, no. – Julian Fondren Jun 11 '13 at 20:58
  • 5
    The question suggests better program design. `if` is not meant to break early, if that is needed, you need another program construct (eg function call, another `if`, a `label`) or remove the expressions from your if block. – vol7ron Jun 11 '13 at 21:08
  • @vol7ron this is in it's nature similar to `next`, or `last` inside while loop. Alternative is more code with `if` blocks and more code indentation. – mpapec Jun 11 '13 at 21:29
  • 2
    @mpapec right, `next` and `last` are really used for iteration control and abused by block-control. `if` is a logic control and is not designed to *exit early*. If there's other logic, then you should use other `if`, or other parts to the language. – vol7ron Jun 11 '13 at 21:38
  • @vol7ron and how about early returns from subroutines? – mpapec Jun 11 '13 at 21:53
  • 1
    @mpapec subroutines are expected to execute one or more statements **and** return a value. `if` is logical flow, expected to execute statements - if there are statements that aren't supposed to be executed, then another logic condition should be used. – vol7ron Jun 11 '13 at 22:00
  • @vol7ron structured programming apologists may say same thing for early return from subroutines – mpapec Jun 11 '13 at 22:16
  • 2
    what's wrong with putting the `if` inside a block? You can label the block if you're concerned about it looking weird. – doubleDown Jun 11 '13 at 22:21

7 Answers7

31

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";
  }
}
mpapec
  • 50,217
  • 8
  • 67
  • 127
  • 5
    You cannot use `last` to get out of `do {}`. – tchrist Jun 11 '13 at 21:38
  • 1
    That's an interesting solution. I can't use `do`, but it works without a do. Looks a wee bit strange in a if/else situation because the if has double `{{`, but not the `else`. – David W. Jun 11 '13 at 21:53
  • @mpapec I've been programming Perl since 3.x, and it never occurred to me before. I was helping someone with their program. They had a long `if` statement and half way through, they had another condition that if met would stopped executing the last half of the `if`. I thought it looked ugly, and added too many indents to the code. I was about to suggest using `last` when I suddenly dawned on me that `last` doesn't work in `if` statements. – David W. Jun 12 '13 at 01:46
  • IMO the last version here is the best given in any answer (only with proper indentation) – ysth Jun 12 '13 at 14:06
  • @mpapec - I decided to use a variation of your last method. My label is a description of the if statement, and I simply used `last`. The label doesn't do anything, but acts as a handy description. I'll probably have the whole block rewritten to use a subroutine instead. The if takes up over 40 lines of code which is just too long in my opinion. – David W. Jun 12 '13 at 19:01
  • 1
    @DavidW. Labels being good and all (I use labeled blocks freely and often) i actually like it better here without one: `if ($cond) {{ ... last if $other; ... }} else { ... }`. Kinda clean, with minimal added syntax – zdim Aug 22 '19 at 18:50
  • It makes sense for small scopes. – mpapec Aug 23 '19 at 07:23
3
  • 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.

DVK
  • 126,886
  • 32
  • 213
  • 327
  • 2
    you mean just an empty block, not a `for (;;)`, which is an endless loop – ysth Jun 11 '13 at 21:16
  • 5
    Not a good solution. What happens when I finish my if statement? I end up looping back through my `if` again. I now have to put another `last` statement in my `if`. My OP when I put an empty block is cleaner. – David W. Jun 11 '13 at 21:49
  • 5
    Using `for` for no reason is less legible than just adding a `{}` block somewhere. – aschepler Jun 11 '13 at 22:20
  • @ysth - I just forgot the last "last" in the code example. Fixed – DVK Jun 12 '13 at 00:16
  • @DavidW. - sorry, the example forgot the "last" at the end. – DVK Jun 12 '13 at 00:17
  • @aschepler - "{{" is hard to visually distinguish from "{" for those who don't know this trick. "for" at least jumps out at you, even if you need a comment to explain what it's for. Neither is, of course, as optimal as having a subroutine readability wise – DVK Jun 12 '13 at 00:18
  • so put the block around the if so there's no {{ – ysth Jun 12 '13 at 06:37
  • 1
    @ysth - that was option #4. I listed ALL options I was aware of. – DVK Jun 12 '13 at 13:28
  • hmm, I don't see how that is less readable; certainly using an explicit label would be a good idea – ysth Jun 12 '13 at 14:07
2

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
        ...
    }
}
Lynn
  • 10,425
  • 43
  • 75
  • I know that. You can do the same thing with `while` and `for` loops too, but the `last` and `next` help clean up the loop logic. – David W. Jun 11 '13 at 21:46
0

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.

vol7ron
  • 40,809
  • 21
  • 119
  • 172
0

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;
}
Louis Strous
  • 942
  • 9
  • 15
  • It's interesting and is simple. My only concern is that the setting of `ok` depends upon each and every `if` condition. MY fear is that someone looking over the program will see the first `my $ok = ...` statement, but miss the others that are embedded in the `if` statements. – David W. Nov 13 '14 at 15:06
0

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
    ...
}
René Nyffenegger
  • 39,402
  • 33
  • 158
  • 293
0

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
cxw
  • 16,685
  • 2
  • 45
  • 81