Controlling Loops

With both while and for loops, the tests at the top are there to stop the loop when some sort of condition has been reached. And, in many loops, it'll be the test that stops the loop. When you start working with more complex loops, however, or play with infinite loops as I have in some of the previous examples we've looked at, chances are good there will be some point in the middle of a loop block where you might want to stop looping, stop executing, or somehow control the actual execution of the loop itself. That's where loop controls come in.

Loop controls are simple constructs that are used to change the flow of execution of a loop. You've already seen two of them: next and last, to restart the loop and to break out of it altogether. In addition, Perl also provides a redo control and labels for loops that control which loop to break out of with the other keywords.

Note

As I mentioned in the section on the do loop, you cannot use any loop controls inside do loops, or label those loops. Rewrite the loop to use while, until, for, or foreach instead.


last, next, and redo

The last, next, and redo keywords are the simplest of loop controls; when one of these occurs inside a while or a for, Perl will interrupt the normal execution of the loop in some way.

You can use all three of these keywords by themselves, in which case they refer to the innermost loop, or they can be used with labels to refer to specific loops (more about labels later). Here's what happens with each keyword:

  • last causes the loop to stop looping immediately (like break in C). Execution continues onto the next part of the script.

  • next stops the execution of the current iteration of the loop, goes back to the top, and starts the next iteration with the test. It's like continue in C. The next keyword is a convenient way of skipping the code in the rest of the block if some condition was met.

  • redo stops execution of the current loop iteration and goes back to the top again. The difference between redo and next is that next restarts the loop by reevaluating the test (and executing the change statement, for for loops); redo just restarts the block from the topmost statement without testing or incrementing anything.

So, for example, let's reexamine the first while loop in picknum.pl, our number picker game:

while () {
    print 'Enter top number: ';
    chomp($top = <STDIN>);

    if ($top =~ /D/) {  # non-numbers, also negative numbers
        if ($top =~ /-d+/) { # only negative numbers
            print "Positive numbers only
";
        } else {
            print "Digits only.
";
        }
        next;
    } elsif ($top <= 1 ) {
        print "Numbers greater than 1 only.
";
        next;
    }
    last;
}

Because the familiar while loop in this example is an infinite loop, we have to use some sort of loop control expression to break out of it when some condition has been met—in this case, when an actual number has been met. As you learned, those tests are that its an actual number, and that its greater than 0. In each of these if statements, if the number meets that criteria, we use next to immediately skip over the rest of the code in the block and go back up to the top of the loop (if there was a test in that while loop, we'd have evaluated it again at that point). If the number entered ends up passing both tests, then that number is acceptable, and we can break out of the loop altogether using last.

Loop controls are generally only necessary where you're checking for some special-case condition that would interrupt the normal flow of the loop or, in this case, to break out of an infinite loop.

Labeling Loops

Loop controls, by themselves, exit out of the closest enclosing block. Sometimes, however, you might have a situation with multiple nested loops, where some condition might occur that would cause you to want to exit out of several loops, or to jump around inside different nested loops. For just this reason, you can label specific loops, and then use last, next, and redo to jump to those outer loops.

Labels appear at the start of a loop, and are conventionally in all caps. This convention keeps them from getting confused with Perl keywords. Use a colon to separate the label name from the loop:

LABEL: while (test) {
   #...
}

Then, inside the loop, use last, next, or redo with the name of the label:

LABEL: while (test) {
   #...
   while (test2) {
      # ...
      if (test) {
         last LABEL;
      }
   }
}

The labels can be anything you want to call them, with two exceptions: BEGIN and END are reserved labels that are used for package construction and deconstruction. You'll learn more about this on Day 13, “Scope, Modules, and Importing Code.”

Here's a simple example without labels. The outer while loop controls the repetition of the script itself; tests to see if the variable $again is equal to the string 'y'. The inner while loop is an infinite loop that tests the input and repeats until there is correct input (I've deleted the part of the script that processes that input to do whatever this script might be doing).

$again = 'y';

while ($again eq 'y') {
    while () {
        print 'Enter the number: ';
        chomp($num = <STDIN>);
        if ($num !~ /d+/) { # test for strings
            print "No strings.  Numbers only please..
";
            next;
        }
        # more tests
        last;
    }
    print 'Try another number (y/n)?: ';
    chomp ($again = <STDIN>);
}

In this example, the next and the last command in the inner loop will exit the nearest enclosing loop—the infinite loop where you're entering the number and testing the input for strings. They will not exit out of the outer loop. The only thing that will exit the outer loop is if $again gets set to 'n' at the end of the outer loop (those last two lines prompt for the appropriate answer). Note also that $again has to be initialized to 'y' for the loop to start looping at all.

Say you wanted to add behavior to this simple script so that if you typed exit anywhere Perl would exit all the loops and end the script. You might label the outer loop like this:

OUTER: while ($again eq 'y') {
   # etc.
}

Then, inside both the outer and inner loops, you'd use last with the name of the label, like this:

OUTER: while ($again eq 'y') {
    while () {
        print 'Enter the number: ';
        chomp($num = <STDIN>);
        if ($num eq "exit" ) { # quit!  exit!  gone!
           last OUTER;
        }
        # etc.
    }
 #  more...
}

In this case, if the user typed exit at the 'Enter the number' prompt, the last command breaks out all the way to the loop labeled OUTER, which in this case is the outer loop.

Note that the label applies to the loop itself, and not to a specific position in the script (in fact, given that a label jumps outside a loop, it actually goes to the next line past that labeled loop, not to the loop itself). Don't think of labeled loops as gotos (if you know what a goto is); think of them more as handles attached to the loop to which you can jump.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.149.222