elTriberium has asked for the wisdom of the Perl Monks concerning the following question:

OK, so no one really likes goto. However, I have come to a situation where goto would probably the easiest solution to a problem I have.

Before, my code looked like this (I know that next already is a form of goto, but I think it's considered "OK" to use it).

LABEL1: foreach my $a (@b) { if (...) { next LABEL1; } do_something(); if (...) { next LABEL1; } do_something_else(); if (...) { next LABEL1; } do_something_again(); }

Now however I want to add a call to a subroutine to the end of the foreach loop. This subroutine (let's call it perform_cleanup) should always be invoked, even in case we're going to the next loop iteration early. So my easiest solution with goto would be to change each "next LABEL1" to "goto LABEL1" and add the following to the end of the foreach loop:

LABEL1: perform_cleanup();

I'm asking this out of curiosity. In this case what would you say is the "cleanest" solution to do this? Do you think using goto in this case is OK or would you rewrite it?

Replies are listed 'Best First'.
Re: Avoiding goto
by duff (Parson) on Jun 11, 2010 at 00:04 UTC
    I might write it like this:
    for my $a (@b) { if (...) { next } do_something(); if (...) { next } do_something(); if (...) { next } do_something(); } continue { perform_cleanup(); }

Re: Avoiding goto
by ikegami (Patriarch) on Jun 11, 2010 at 00:20 UTC

    duff has a nice clean solution. If you need something more resilient (i.e. cleanup even if the code dies), you can use the following:

    use Sub::ScopeFinalizer qw( scope_finalizer ); for my $a (@b) { my $anchor = scope_finalizer { perform_cleanup(); }; next if ...; do_something(); next if ...; do_something_else(); next if ...; do_something_again(); }
      Thanks everyone. I'll go through all the different solutions you suggested and will use the one which works best in my case.
Re: Avoiding goto
by Anonymous Monk on Jun 11, 2010 at 00:20 UTC
    Or

    for my $foo (@bar) { { cond1 ? next : action1(); cond2 ? next : action2(); cond3 ? next : action3(); } action_fallthough(); }

    See perldoc on next under "Note that a block by itself is semantically identical to" or perlsyn and search for "Sorry. You can always put another block inside".

    Simple example:

    for (1..10) { print "$_ is an "; { next if $_ % 2; print "even "; } print "integer\n"; }

      The down side is that last becomes equivalent to next (unless you use a label).

      $ perl -le'for (1..2) { { print; last; } cleanup; }' 1 2

      The up side is that it solves the following problem nicely:

      $ perl -Mstrict -e'for (@ARGV) { my $x; } continue { cleanup($x) }' Global symbol "$x" requires explicit package name at -e line 1. -e had compilation errors.
Re: Avoiding goto
by JavaFan (Canon) on Jun 11, 2010 at 11:30 UTC
    Structured programming (as recommended by Dijkstra in his "Go To Considered Harmful") would want you to write it as:
    foreach my $a (@b) { if (!...) { do_something(); if (!...) { do_something_else(); if (!...) { do_something_again(); } } } perform_cleanup(); }
    The essence being that each block has exactly one entry, and exactly one exit point.
Re: Avoiding goto
by shawnhcorey (Friar) on Jun 11, 2010 at 10:54 UTC

    Try:

    LABEL1: foreach my $a (@b) { if (...) { next LABEL1; } do_something(); if (...) { next LABEL1; } do_something_else(); if (...) { next LABEL1; } do_something_again(); }continue{ clean_up(); }

    BTW, you can't avoid goto's. The best you can do is to cleverly disguise them.

Re: Avoiding goto
by JavaFan (Canon) on Jun 11, 2010 at 14:14 UTC
    Probably not the cleanest way, but it's possible to write it as:
    foreach my $a (@b) { !... && do {do_something(); 1} && !... && do {do_something_else(); 1} && !... && do_something_again(); perform_cleanup(); }
    The do's can be simplified if do_something and do_something_else always return true.