Raku Programming/Blocks and Closures

From Wikibooks, open books for an open world
Jump to navigation Jump to search

About Blocks[edit | edit source]

When we talked about subroutines we saw that a subroutine declaration consisted of three parts: The subroutine name, the subroutine parameter list, and the code block of subroutine internals. Blocks are very fundamental in Raku, and we're now going to use them to do all sorts of cool things.

We've seen a few blocks used in various constructs already:

# if/else statements
if $x == 1 {
}
else {
}

# subroutines
sub thisIsMySub () {
}

# loops
for @ary {
}

loop (my $i = 0; $i <= 5; $i++) {
}

repeat {
} while $x == 1;

All these blocks serve the purpose of grouping lines of code together for a particular purpose. In an if block, the statements inside the block are all executed when the if condition is true. The entire block is not executed if the condition is false. In a loop, all the statements in the loop block are executed together in repetition.

Scope[edit | edit source]

In addition to keeping like code together, blocks also introduce the notion of scope. my variables defined inside a block are not visible outside it. Scope ensures that variables are only used when they are needed, and they are not being modified when they are not supposed to be. Blocks don't need to be associated with any particular construct, like an if or a loop. Blocks can exist all by themselves:

my $x = 5;
my $y = 5;
{
   my $y = 3;
   say $x;         # 5
   say $y;         # 3
}
say $x;            # 5
say $y;            # 5

The example shows the idea of scope very nicely: The variable $y inside the block is not the same as the variable $y outside the block. Even though they have the same name, they have a different scope. Here's a slightly different example:

my $x = 5;
{
   my $y = 7;
   {
      my $z = 9;
      say $x;  # 5
      say $y;  # 7
      say $z;  # 9
   }
   say $x;     # 5
   say $y;     # 7
   say $z;     # ERROR: Undeclared variable!
}
say $x;        # 5
say $y;        # ERROR! Undeclared variable!
say $z;        # ERROR! Undeclared variable!

The variable $x is visible from the point where it was defined and inside all scopes inside the scope where it was defined too. $y however is only visible inside the block it was defined in, and the block inside that. $z is only visible in the innermost block.

Scope Variables[edit | edit source]

Scopes can be specified exactly in cases where there is ambiguity. We can use keywords like OUTER to specify a variable from the scope directly above the current scope:

my $x = 5;
{
   my $x = 6;
   say $x;           # 6
   say $OUTER::x    # 5
}

Subroutines have access to the scope from which they are called using the CALLER scope, assuming that the variable in the outer scope was declared as is context:

my $x is context = 5;
mySubroutine(7);

sub mySubroutine($x) {
   say $x;         # 7
   say $CALLER::x; # 5
}

Coderefs[edit | edit source]

Blocks can be stored in a single scalar variable as a coderef. Once stored in a coderef variable, the block can be executed like a regular subroutine reference:

my $dostuff = {
   print "Hello ";
   say "world!";
}

$dostuff();

Closures[edit | edit source]

We see in the example above that a block can be stored in a variable. This action creates a closure. A closure is a stored block of code that saves its current state and current scope, which can be accessed later. Let's see a closure in action:

my $block;
{
    my $x = 2;
    $block = { say $x; };
}
$block();   # Prints "2", even though $x is not in scope anymore

The closure saves a reference to the $x variable when the closure is created. Even if that variable is not in scope anymore when the code block is executed.

When we change $x later on, the closure will see the changed value, so if you want to create multiple closures with different enclosed variables, you have to create a new variable each time:

my @times = ();
for 1..10 {
   my $t = $_;          # each subroutine gets a different $t
   @times[$_] = sub ($a) { return $a * $t; };
}

say @times[3](4);       # 12
say @times[5](20);      # 100
say @times[7](3);       # 21

Captures[edit | edit source]

Pointy Blocks[edit | edit source]

We can use the sub keyword to create a subroutine or a subroutine reference. This isn't the only syntax to do this, and in fact is a little bit more verbose then it needs to be for the common case of an unnamed ("anonymous") subroutine or subroutine reference. For these, we can use a construct called a pointy block. Pointy blocks, which are called lambda blocks in other languages, are very useful. They can create a code reference like an anonymous subroutine, and they can also create blocks of code with parameters. A pointy block is a lot like an unnamed subroutine. More generally, it's like a block with parameters. We've seen pointy blocks briefly when we talked about loops. We used pointy blocks in association with a looping construct to give names to the loop variable instead of relying on the default variable $_. This is why we used pointy blocks in these situations: They enable us to specify variable names to use as parameters to an arbitrary block of code.

We'll show a few examples:

my @myArray = (1, 2, 3, 4, 5, 6);

# In a loop:
for @myArray -> $item {
    say $item;

# Output is:
#    1
#    2
#    3
#    4
#    5
#    6

}
# In a loop, multiples
for @myArray -> $a, $b, $c {
    say "$a, $b, $c";

# Output is:
#    1, 2, 3
#    4, 5, 6

}
# As a condition:
my $x = 5;
if ($x) -> $a { say $a; }  # 5
# As a coderef
my $x = -> $a, $b { say "First: $a.  Second: $b"; }
$x(1, 2);       # First: 1, Second: 2
$x("x", "y");   # First: x, Second: y
# As an inline coderef
-> $a, $b { say "First: $a, Second: $b"; }(1, 2)
#In a while loop
while ($x == 5) -> $a {
   say "Boolean Value: $a";
}

Placeholder Arguments[edit | edit source]

In a block, if we don't want to go through the hassle of writing out an argument list, we can use placeholder arguments. Placeholders use the special ^ twigil. Passed values are assigned to placeholder variables in alphabetical order:

for 1..3 {
  say $^a;    # 1
  say $^c;    # 3
  say $^b;    # 2
}