Selasa, 03 November 2009

The while(1) {...last...} construct

In the past year or two I've been comfortably using this construct in Perl as well as in PHP (and probably others too):

while (1) {
do_stuff;
do_some_checks;
do { warn error 1; last } if not OK;

do_more_stuff;
do_some_checks;
do { warn error 2; last } if not OK;

do_even_more_stuff;
do_some_checks;
do { warn error 3; last } if not OK;

...

#finally
last;
}


I prefer it over the one below:

do_stuff;
do_some_checks;
if (!OK) {
warn error 1;
} else {
do_more_stuff;
do_some_checks;
if (!OK) {
warn error 2;
} else {
do_even_more_stuff;
do_some_checks;
if (!OK) {
...
}
# finally
success!
}
}


The while (1) { ... last ... } style is clearer and avoids extraneous indentation. The only thing biting me in the past when using this construct is that sometimes I forgot to add the final last, resulting in an infinite loop. But this series-of-checks-and-early-bail pattern happens so often in my code that the construct quickly became second nature.

Anyone got the same habit, or perhaps using some alternative (like the new given-when)?

4 komentar:

  1. Just leave out the 'while (1)' part. If you read the documentation for last it says: "Note that a block by itself is semantically identical to a loop that executes once. Thus "last" can be used to effect an early exit out of such a block."

    Try it with: perl -lE '{ say "Hello"; last; say "Bye"}'

    Only the 'Hello' gets printed.

    BalasHapus
  2. There's a much nicer way to do this, I like to call it the "make subroutines" pattern ;)

    Put the series of checks into a subroutine, then simply call "return" whenever a check fails.

    This is much clearer than either of your two options up there, and has the bonus of giving you a chance to introduce a useful name for a block of code.

    Whenever you find yourself going into contortions to avoid lots of nested ifs, consider making a new subroutine.

    BalasHapus
  3. @pmakholm: Thanks for pointing that out! Perl never ceases to give nice surprises (as well as not so nice ones but those are rarer).

    @Dave: Usually the construct already forms the outermost layer of a sub. I just need to do some stuffs before returning. E.g.:

    # returns [HTTP status code, error msg, result]
    sub foo {
    my @resp = (500, "Error");
    {
    stuff...
    { @resp = (404, "Not found"); last } if not OK;
    stuff..
    last if not OK;
    ...
    @resp = (200, "OK", $res);
    }
    wantarray ? @resp : $resp[2];
    }

    BalasHapus
  4. Unfortunately there is no equivalent for the short '{...last...}' Perl idiom in PHP.

    I also realized that you can do this instead to avoid writing the final 'last':

    # Perl
    for(1) { # do only once
    ...
    }

    # PHP
    do { # do only once
    ...
    } while (0);

    Feel kind of silly for having written hundreds of avoidable 'last' statements now. :-)

    Thanks everybody.

    BalasHapus