Spot the error

Spent 10 minutes the other day scratching my head after my Perl code stopped working following a single added line (can you guess which one?):

LABEL1:
my @var;
for (...) {
next LABEL1;
}

The error message is "Label not found for 'next LABEL1'. I think Perl could be better at handling this kind of mistake.

10 Comments

I think this is not a mistake.

next, last should not jump to out of the block.

Never ever ever ever ever use labels.

@Mike: why not? Here's some code from my Class::Sniff module:

CLASS:
 for my $class (@$classes) {
  my $method_found = 0;
  for my $path (@paths) {

    # method was found in a *previous* path.
    if ($method_found) {
      push @unreachable => "$class\::$method";
      next CLASS;
    }
    for my $curr_class (@$path) {
      next CLASS if $curr_class eq $class;
      if ( not $method_found && $curr_class->can($method) ) {
        $method_found = 1;
      }
    }
  }
}

The label makes that code much easier to read and work with. How would you rewrite it?

For breaking out of inner loops I prefer to set a flag and use last. Or separate the inner loop into a subroutine and return something useful.

I know some people consider that more obfuscated, but in my opinion the use of labels on loops is identical to a goto; it makes a program unstructured. Sometimes a little more code can be clearer than a concise solution.

That said, I'm not one to stick to dogma at all costs. All tools are useful, and I'm sure there are uses of labels out there that I could stomach.

@Mike -- Labels are an awesome, underappreciated Perl feature. From "perldoc -f goto":

"It can be used to go almost anywhere else within the dynamic scope, including out of subroutines,..."
Labels provide a form of non-local control transfer that can be much more clear and concise than eval/die. For example, given a function like this:
sub search_big_thing(&) {
    SEARCH_BIG_THING: while ($_ = next_big_thing) {
        $_[0]->();
    }
}
your callback, or a function it calls, can easily break out of search_big_thing with "last SEARCH_BIG_THING".

@educated, that strikes me as a really bad idea. A callback should never be responsible for altering the flow of the caller. What's wrong with


sub search_big_thing(&) {
while( $_ = next_big_thing ) {
my $found_it = $_[0]->();
last if $found_it;
}
}

Bonus: now the callback sub is not bound to an arbitrary label name, and is thus reusable.

Mike, AFAICT you missed my point. Let's try another example:


sub search_world(&) {
SEARCH_WORLD: while ($_ = next_country) { $_[0]->() };
}
sub search_country { search_state $_ for states $_ }
sub search_state { search_city $_ for cities shift }
sub search_city { search_district $_ for districts shift }
sub search_district { search_house $_ for houses shift }
sub search_house {
for (people shift) {
last SEARCH_WORLD if is_terrorist $_
}
}
search world { search_country $_ };

Also, "A callback should never be responsible for altering the flow of the caller" imples that a callback should never throw exceptions. Really?

That's a pretty contrived example, I think. You'd have to be batty to design a search algorithm with five nested loops.

But in the miniscule chance that you actually have something like that, I suppose using a label is fine.

We'll just have to agree to disagree, then... But let's say you have a map, nicely stored as a quad tree, and you want to find something on that map. How do you do it? More generally, let's say you have a hierarchical data structure, and you want to search it. Or is that too contrived?

Leave a comment

About Steven Haryanto

user-pic A programmer (mostly Perl 5 nowadays). My CPAN ID: SHARYANTO. I'm sedusedan on perlmonks. My twitter is stevenharyanto (but I don't tweet much). Follow me on github: sharyanto.