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 development tasks , 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 / when .

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 $_ . $_ 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 more convenient . Generally, 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 has to be 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:

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

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. The difference is also significant when trapping exceptions. More on that in Section 4.3.3.3 later in this chapter.

A when statement can also appear outside a given . When it does, it simply smart match against $_ . when statements also have a statement modifier form that doesn't have an implicit break :

 print "Zaphod" when 'two heads';    # if $_ ~~ 'two heads' 

4.3.2 Iteration

Iteration constructs allow you to execute a set of statements multiple times. Perl 6's loop constructs are while , until , loop , and for .

4.3.2.1 The while loop

A 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:

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

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 . . . "; } 

The parentheses around the loop condition are optional.

4.3.2.3 The for loop

The for loop is the list iterator, so it imposes lazy list context. It takes a 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. This means all the constructs that default to $_ , like print and when , can default to the loop variable:

 for @useful_things {     print; # prints $_, the current loop variable     print " You're one hoopy frood." when 'towel'; } 

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

[5] The arrow isn't restricted to for ; it also works on given and other control flow structures.

 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 list 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?"; } 

This example iterates over three arrays, taking one element from each array on each iteration and creating named aliases for the three elements.

 # 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."; } 

This example iterates over two arrays, taking two elements from each array on each iteration and creating named aliases for them.

 # 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); } 

This example iterates over two arrays, taking two elements from the first array and one element from the second array on each iteration and creating named aliases for them.

If zip is called with arrays or lists of different lengths, it will fill in undef values for the named aliases pulled from the shorter lists.

4.3.2.4 Breaking out of loops

The next , redo , 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. redo skips the remaining code in the loop and restarts the same iteration over again without incrementing counters or reevaluating loop conditions. last skips the remaining code in the loop and terminates the loop.

 for @useful_things -> $item {     next when 'towel';     redo when .try_again;     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 subroutine reference, 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. This example creates a closure that prints a lexical variable. When the closure is executed (from some other lexical scope), it prints the variable from the scope where it was defined, not the scope where it's executed:

 my $person = "Zaphod"; $closure = { print $person; }  . . .  my $person = "Trillian"; $closure( ); # prints "Zaphod" 

The fact that all blocks are closures has some implications. Every block can take arguments. 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; 

state declares a lexical variable similar to my , but instead of reinitializing the value every time the block is executed it preserves the previous value:

 state $static_var; 

temp and let are not declarations; they are run-time commands to store 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 the lexical scope of the let is exited under an error condition (an undef or empty-list return value, or an exception):

 temp $throwaway; let $hypothetical; 

temp and let don't change the value of the variable, they only store it.

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 that contains them. Property blocks are defined within the block they modify, 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 executes before everything else ”before all other properties and code in an ordinary block and before the first iteration of a loop. POST executes after everything else ”after all code and properties in an ordinary block and after the last iteration of a loop. PRE and POST are intended for assertion checking and cannot have any side effects. 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. There can be only one CATCH in a block, but there's no limit on the other types of property blocks.

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 = "$count potato, ";     NEXT {         print $potato;     }     LAST {         print $potato, "more.";     } } 

In this example, the lexical variable $potato is redefined on every iteration and then printed from within the NEXT or LAST block. So the final result is:

 5 potato, 6 potato, 7 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 $! . All exception classes inherit from the Exception class.

Error exceptions are thrown by die or (under use fatal ) fail . 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 (see the Section 4.2.12 earlier in this chapter):

 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. A when case with a continue statement leaves the exception unhandled, since continue skips the implicit break . If the exception isn't marked clean by the end of the CATCH block, CATCH rethrows the exception so an outer block can catch it.

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.

If you want to limit the effects of an error exception, you can wrap the error throwing code in a try block. A try block without a CATCH block provides a default CATCH that catches all exceptions and, marks them as clean, and causes the try to return undef when any exception was caught. A try block is also a handy bit of self-documentation.

 try {     may_throw_exception( );     CATCH {         when Error::Moof { warn "Caught a Moof error."; }     } } 

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.



Perl 6 and Parrot Essentials
Perl 6 and Parrot Essentials, Second Edition
ISBN: 059600737X
EAN: 2147483647
Year: 2003
Pages: 116

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net