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.
Selection executes one set of actions out of many possible
sets. The selection control structures are if
,
unless
, and
given
/when
.
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."; }
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
.
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'
Iteration constructs allow you to execute a set of statements
multiple times. Perl 6’s loop constructs are
while
, until
,
loop
, and for
.
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'); }
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.
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.[8] 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 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.
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?"; }
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"; }
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.
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.
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.
Get Perl 6 and Parrot Essentials, Second Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.