Team LiB   Previous Section   Next Section

4.3 Control Structures

The simplest flow of control is linear—one statement follows the next in a straight line to the end of the program. Since this is far too limiting for most situations, languages provide ways to alter the control flow.

4.3.1 Selection

Selection executes one set of actions out of many possible sets. The selection control structures are if, unless, and given.

4.3.1.1 The if statement

The if statement checks a condition and executes its associated block only if that condition is true. The condition can be any expression that evaluates to a truth value. Parentheses around the condition are optional:

if $blue {
    print "True Blue.";
}

The if statement can also have an unlimited number of elsif statements that check additional conditions when the preceding conditions are false. The final else statement executes if all preceding if and elsif conditions are false:

if $blue {
    print "True Blue.";
} elsif $green {
    print "Green, green, green they say...";
} else {
    print "Colorless green ideas sleep furiously.";
}
4.3.1.2 The unless statement

The unless statement is the logical opposite of if. Its block executes only when the tested condition is false:

unless $fire {
    print "All's well.";
}

There is no elsunless statement, though else works with unless.

4.3.1.3 The switch statement

The switch statement selects an action by comparing a given expression, the switch, to a series of when statements, the cases. When a case matches the switch, its block is executed:

given $bugblatter {
    when Beast::Trall { close_eyes(  ); }
    when 'ravenous'   { toss('steak'); }
    when .feeding     { sneak_past(  ); }
    when /grrr+/      { cover_ears(  ); }
    when 2            { run_between(  ); }
    when (3..10)      { run_away(  ); }

}

If these comparisons are starting to look familiar, they should. The set of possible relationships between a given and a when are exactly the same as the left and right side of a smart match operator (~~). The given aliases its argument to $_.[10]

[10] $_ is always the current topic (think "topic of conversation"), so the process of aliasing a variable to $_ is known as "topicalization."

The when is a defaulting construct that does an implicit smart match on $_. The result is the same as if you typed:

given $bugblatter {
    when $_ ~~ Beast::Trall { close_eyes(  ); }
    when $_ ~~ 'ravenous'   { toss('steak'); }
    when $_ ~~ .feeding     { sneak_past(  ); }
    when $_ ~~ /grrr+/      { cover_ears(  ); }
    when $_ ~~ 2            { run_between(  ); }
    when $_ ~~ (3..10)      { run_away(  ); }
}

but much more convenient. In general, only one case is ever executed. Each when statement has an implicit break at the end. It is possible to fall through a case and continue comparing, but since falling through is less common, it is explicitly specified with a continue:

given $bugblatter {
    when Beast::Trall { close_eyes(  ); continue; }
    when 'ravenous'   { toss('steak'); continue; }
    when 'attacking'  { hurl($spear, $bugblatter); continue; }
    when 'retreating' { toss('towel'); }
}

The default case executes its block when all other cases fail:

given $bugblatter {
    when Beast::Trall { close_eyes(  ); }
    when 'ravenous'   { toss('steak'); }
    default { run('away'); }
}

Any code within a given will execute, but a successful when skips all remaining code within the given, not just the when statements. This means the default case isn't really necessary, because any code after the final when just acts like a default. But an explicit default case makes the intention of the code clearer in the pure switch. There's more than one way to do it (TMTOWTDI).

given $bugblatter {
    print "Slowly I turn...";
    when Beast::Trall { close_eyes(  ); }
    print "Step by step...";
    when 'ravenous'   { toss('steak'); }
    print "Inch by inch...";
}

The when statement can also appear outside a given. When they do, they simply smart match against $_. when statements also have a statement modifier form. It doesn't have an implicit break:

print "Zaphod" when 'two heads';

4.3.2 Iteration

Iteration executes one set of actions multiple times. Perl 6's loop constructs are while, until, loop, and for.

4.3.2.1 The while loop

The while loop iterates as long as a condition is true. The condition may be complex, but the result is always a single boolean value because while imposes boolean context on its condition:

while $improbability > 1 {
    print "$improbability to 1 against and falling.";
    $improbability = drive_status('power_down');
}

until is like while but continues looping as long as the condition is false.

4.3.2.2 The simple loop

In its simplest form, the loop construct is infinite. It will iterate until a statement within the loop explicitly terminates it:

loop {
    print "One more of that Ol' Janx.";
    last if enough(  );
}

loop is also the counter iterator. Like while, it tests a condition before executing its block each time, but it has added expression slots for initialization and execution between iterations that make it ideal for counter loops:

loop ( $counter = 1; $counter < 20; $counter++ ) {
    print "Try to count electric sheep...";
}
4.3.2.3 The for loop

The for loop is the list iterator, so it imposes list context. It takes any list or array, or any expression that produces a list, and loops through the list's elements one at a time. On each iteration, for aliases $_ to the current loop element.[11]

[11] Topicalization again.

This means all the constructs that default to $_, like print and when, can default to the loop variable:

for @useful_things {
    print;
    print " You're one hoopy frood." when 'towel';
}

The arrow operator, ->, makes a named alias to the current element, in addition to the $_ alias. All aliases are lexically scoped to the block:

for %people.keys -> $name {
    print; # prints $_ (same as $name)
    print ":", %people{$name}{'age'};
}

The arrow operator also makes it possible to iterate over multiple loop elements at the same time:

for %ages.kv -> $name, $age {
    print $name, " is now ", $age;
}

You can combine the arrow operator with the zip function or zip operator to loop over several lists, taking some specified number of elements from each on every iteration, as in the following code.

# one from each array
for zip(@people,@places,@things) -> $person, $place, $thing {
    print "Are you a $person, $place, or $thing?";
}

# two from each array
for zip(@animals, @things, by=>2) 
        -> $animal1, $animal2, $thing1, $thing2 {

    print "The animals, they came, they came in by twosies, twosies: ";
    print "$animal1 and $animal2";

    print "Two things. And I call them, $thing1 and $thing2.";

}                                                

# two from the first array and one from the second
for zip(@colors=>2, @textures=>1) -> $color1, $color2, $texture {
    $mix = blend($color1, $color2);
    draw_circle($mix, $texture);
}
4.3.2.4 Breaking out of loops

The next and last keywords allow you to interrupt the control flow of a loop. next skips the remaining code in the loop and starts the next iteration. last skips the remaining code in the loop and terminates the loop:

for @useful_things -> $item {
    next when 'towel';
    last when 'bomb';
    print "Are you sure you need your $item?";
}

4.3.3 Blocks

In Perl 6, every block is a closure, so you get consistent behavior throughout the language, whether the block is a control structure, an argument passed to a subroutine, an anonymous subref, or the definition of a named element such as a subroutine, method, or class. What is a closure? Closures are chunks of code that are tied to the lexical scope in which they're defined. When they're stored and later executed at some point far removed from their definition, they execute using the variables in their original scope, even if those variables are no longer accessible any other way. It's almost as if they package up their lexical scope to make it portable.

The fact that all blocks are closures has some implications. Every block can have arguments passed to it. This is how for creates a $_ alias for the iterator variable. Every block defines a lexical scope. Every block has the potential to be stored and executed later. Whether a block is stored or executed immediately depends on the structure that uses it. The control structures we've discussed so far all execute their blocks where they're defined. A bare block executes immediately when it's alone, but is stored when it's in an assignment context or passed as a parameter:

# executed immediately
{
    print "Zaphod";
}

# stored
$closure = {
    print "Trillian";
}
4.3.3.1 my, our, temp, and let

my and our are different ways of declaring variables. my declares a variable in the current lexical scratchpad, while our declares a lexical alias to a variable in the package symbol table.

my $lexical_var;
our $package_var;

temp and let are not declarations, they are runtime commands to store off the current value of a variable so it can be restored later. temp variables always restore their previous value on exiting the lexical scope of the temp, while let variables keep the temporary value, unless they are explicitly told to restore it:

temp $throwaway;
let $hypothetical;
4.3.3.2 Property blocks

Every block may have a series of control flow handlers attached to it. These are called "property blocks" because they are themselves blocks (i.e., closures), attached as properties on the block. Property blocks are defined within their enclosing block by an uppercase keyword followed by a block (they're also sometimes called NAMED blocks):

NEXT {
    print "Coming around again."
}

Property blocks aren't executed in sequential order with the other code in the enclosing block—they are stored at compile time and executed at the appropriate point in the control flow. NEXT executes between each iteration of a loop, LAST executes at the end of the final iteration (or simply at the end of an ordinary block). PRE and POST are intended for assertion checking and cannot have any side effects. PRE executes before everything else in the block, and POST executes after everything else in the loop. CATCH, KEEP, and UNDO are related to exception handling. KEEP and UNDO are variants of LAST and execute after CATCH. KEEP executes when the block exits with no exceptions, or when all exceptions have been trapped and handled; UNDO executes when the block exits with untrapped exceptions.

This example prints out its loop variable in the body of the block:

for 1..4 {
    NEXT { print " potato, "; }
    LAST { print "." }
    print;

}

Between each iteration, the NEXT block executes, printing "potato". At the end of the final iteration, the LAST block prints a period. So the final result is "1 potato, 2 potato, 3 potato, 4".

Property blocks are lexically scoped within their enclosing block, so they have access to lexical variables defined there.

for 5..7 -> $count {
    my $potato = " potato, ";
    NEXT {
        print $count, $potato;
    }
    LAST {
        print $count, $potato, "more.";
    }
}
4.3.3.3 Exceptions

There are two types of exceptions: error exceptions and control flow exceptions. All exceptions are stored in the error object $!. Exceptions are classes that inherit from the Exception class.

Error exceptions are thrown by throw, die, and fail (under use fatal). Any block can be an error exception handler. All it needs is a CATCH block. CATCH blocks always topicalize $!, so the simplest way to test for a particular exception is to compare it to a class name using a when statement.[12]

[12] See the earlier section Section 4.2.11 for a complete set of comparison relations.

CATCH {
    when Err::Danger { warn "fly away home"; }
}

The $! object will also stringify to its text message if you match it against a pattern.

CATCH {
    when /:w I'm sorry Dave/ { warn "HAL is in the house."; }
}

If the CATCH block is exited by an explicit break statement, or by an implicit break in a when or default case, it marks the exception as clean. Otherwise, it rethrows the exception to be caught by some outer block.

Once an exception is thrown, execution skips straight to the CATCH block and the remaining code in the block is skipped. If the block has POST, KEEP, or UNDO property blocks, they will execute after the CATCH block.

Control flow exceptions handle alterations in the flow of control that aren't errors. When you call next to skip the remaining code in the loop and go on to the next iteration, you're actually throwing a control exception. These exceptions are caught by the relevant control structure: next and last exceptions are caught by loops, a return exception is caught by a subroutine or method, etc.

    Team LiB   Previous Section   Next Section