Raku Programming/Control Structures

Flow Control

edit

We've seen in earlier chapters how to create variables and use them to perform basic arithmetic and other operations. Now we're going to introduce the idea of flow control, using special constructs for branching and looping.

Blocks

edit

Blocks are chunks of code inside { } curly brackets. These are set apart from the rest of the nearby code in a variety of ways. They also define a new scope for variables: Variables defined with my and used inside a block are not visible or usable outside the block. This enables code to be compartmentalized, to ensure that variables only intended for temporary use are only used temporarily.

Branching

edit

Branching can occur using one of two statements: an if and an unless. if may optionally have an else clause as well. An if statement evaluates a given condition and if it's a true statement, the block following the if is executed. When the statement is false, the else block, if any, is executed instead. The unless statement does the opposite. It evaluates a condition and only executes its block when the condition is false. You cannot use an else clause when using unless.

Relational Operators

edit

There are a variety of relational operators that can be used to determine a truth value. Here are some:

$x == $y;                  # $x and $y are equal
$x > $y;                   # $x is greater than $y
$x >= $y;                  # $x is greater than or equal to $y
$x < $y;                   # $x is less than $y
$x <= $y;                  # $x is less than or equal to $y
$x != $y;                  # $x is not equal to $y

All of these operators return a boolean value, and can be assigned to a variable:

$x = (5 > 3);              # $x is True
$y = (5 == 3);             # $y is False

The parentheses above are used only for clarity; they are not actually necessary.

if/unless

edit

Let's start off with an example:

my Int $x = 5;
if ($x > 3) {
     say '$x is greater than 3';         # This prints
}
else {
     say '$x is not greater than 3';     # This doesn't
}

Notice in this example above that there is a space between the if and the ($x > 3). This is important and is not optional. The parsing rules for Raku are clear on this point: Any word followed by a ( opening parenthesis is treated as a subroutine call. The space differentiates this statement from a subroutine call and lets the parser know that this is a conditional:

if($x > 5) {   # Calls subroutine "if"
}

if ($x > 5) {  # An if conditional
}

To avoid all confusion, the parenthesis can be safely omitted:

if $x > 5 {     # Always a condition
}

unless

edit

unless has the opposite behavior of if:

my Int $x = 5;
unless $x > 3 {
     say '$x is not greater than 3';         # This doesn't print
}

No else clause is allowed after unless.

Postfix Syntax

edit

if and unless aren't just useful for marking blocks to be conditionally executed. They can also be applied in a natural way to the end of a statement to only affect that one statement:

$x = 5 if $y == 3;
$z++ unless $x + $y > 8;

These two lines of code above only execute if their conditions are satisfied properly. The first sets $x to 5 if $y is equal to 3. The second increments $z unless the sum of $x + $y is greater than 8.

Smart Matching

edit

Sometimes you want to check if two things match. The relational operator == checks if two values are equal, but that's very limited. What if we wanted to check other equality relationships? What we want is an operator that just does what we mean, no matter what that might be. This magical operator is the smart match operator ~~.

Now, when you see the ~~ operator, you probably immediately think about strings. The smart match operator does a lot with strings, but isn't restricted to them.

Here are some examples of the smart match operator in action:

5 ~~ "5";                          # true, same numerical value
["a", "b"] ~~ *, "a", *;           # true, "a" contained in the array
("a" => 1, "b" => 2) ~~ *, "b", *; # true, hash contains a "b" key
"c" ~~ /c/;                        # true, "c" matches the regex /c/
3 ~~ Int                           # true, 3 is an Int

As you can see, the smart match operator can be used in a variety of ways to test two things to see if they match in some way. Above we saw an example of a regular expression, which we will discuss in more detail in later chapters. This also isn't a comprehensive list of things that can be matched, we will see more things throughout the book.

Given / When

edit

Raku has a facility for matching a quantity against a number of different alternatives. This structure is the given and when blocks.

given $x {
  when Bool { say '$x is the boolean quantity ' ~ $x; }
  when Int { when 5 { say '$x is the number 5'; } }
  when "abc" { say '$x is the string "abc"'; }
}

Each when is a smart match. The code above is equivalent to this:

if $x ~~ 5 {
  say '$x is the number 5';
} 
elsif $x ~~ "abc" {
  say '$x is the string "abc"';
}
elsif $x ~~ Bool {
  say '$x is the boolean quantity ' ~$x;
}

The given/when structure is more concise than the if/else, and internally it might be implemented in a more optimized way.

Loops

edit

Loops are ways to repeat certain groups of statements more than once. Raku has a number of available types of loops that can be used, each of which has different purposes.

for loops

edit

for blocks take an array or range argument, and iterate over every element. In the most basic case, for assigns each successive value to the default variable $_. Alternatively, a specific variable can be listed to receive the value. Here are several examples of for blocks:

# Prints the numbers "12345"
for 1..5 {          # Assign each value to $_
    .print;           # print $_;
}

# Same thing, but using an array
my @nums = 1..5;
for @nums {
    .print;
}

# Same, but uses an array that's not a range
my @nums = (1, 2, 3, 4, 5);
for @nums {
    .print;
}

# Using a different variable than $_
for 1..5 -> $var {
    print $var;
}

In all the examples above, the array argument to for can optionally be enclosed in parenthesis too. The special "pointy" syntax -> will be explained in more detail later, although it's worth noting here that we can extend it to read multiple values from the array at each loop iteration:

my @nums = 0..5;
for @nums -> $even, $odd {
  say "Even: $even Odd: $odd";
}

This prints the following lines:

Even: 0 Odd: 1
Even: 2 Odd: 3
Even: 4 Odd: 5

for can also be used as a statement postfix, like we saw with if and unless, although with some caveats:

print $_ for (1..5);    # Prints "12345"
print for (1..5);        # Parse Error! Print requires an argument
.print for 1..5;  # Prints "12345"

loop

edit

C programmers will recognize the behavior of the loop construct, which is the same format and behavior as the for loop in C. Raku has reused the name for for the array looping construct that we saw in the previous section, and uses the name loop to describe the incremental behavior of C's loops. Here is the loop structure:

loop (my $i = 0; $i <= 5; $i++) {
    print $i;            # "12345"
}

In general, loop takes these three components:

loop ( INITIALIZER ; CONDITION ; INCREMENTER )

The INITIALIZER in a loop is a line of code that executes before the loop begins, but has the same lexical scope as the loop body. The CONDITION is a boolean test that's checked before every iteration. If the test is false, the loop exits, if it is true, the loop repeats. The INCREMENTER is a statement that happens at the end of the loop, before the next iteration begins. All of these parts may be optionally omitted. Here are five ways to write the same loop:

loop (my $i = 0; $i <= 5; $i++) {
    print $i;            # "12345"
}

my $i = 0;    # Small Difference: $i is scoped differently
loop ( ; $i <= 5; $i++) {
    print $i;
}

loop (my $i = 0; $i <= 5; ) {
    print $i;            # "12345"
    $i++;
}

loop (my $i = 0; ; $i++) {
    last unless ($i <= 5);
    print $i;            # "12345"
}

my $i = 0;
loop ( ; ; ) {
    last unless ($i <= 5);
    print $i;            # "12345"
    $i++;
}

If you want an infinite loop, you can also omit the parentheses instead of using (;;):

my $i = 0;
loop {   # Possibly infinite loop
    last unless ($i <= 5);
    print $i;            # "12345"
    $i++;
}

repeat blocks

edit

A repeat block will execute its body at least once as the condition follows after the block. In the example below you can see that even though $i is larger than two, the block will still run.

my $i = 3;
repeat {
    say $i;
} while $i < 2;