If you are only interested in learning how Perl special blocks work in Raku, you can skip this blog post. But you will be missing out on quite a few nice and useful features that are available in Raku.
Block and Loop phasers
Block and Loop phasers are always associated with the surrounding Block, regardless of where they are located in the Block. Except you are not limited to having just one of each — although you could argue that having more than one doesn't improve maintainability.
Note that any sub
or method
is also considered a Block with regards to these phasers: you could think of the Block
as a base-class for subroutines and methods.
Name Description
-----------------------------------------------------
ENTER Run every time when entering a block
LEAVE Run every time when leaving a block
PRE Check condition before running a block
POST Check return value after having run a block
KEEP Run every time a block is left successfully
UNDO Run every time a block is left unsuccessfully
-----------------------------------------------------
ENTER & LEAVE
The ENTER
and LEAVE
phasers are pretty self-explanatory: the ENTER
phaser is called whenever a block is entered. The LEAVE
phaser is called whenever a block is left (either gracefully or through an exception).
A simple example:
# Raku
say "outside";
{
LEAVE say "left";
ENTER say "entered";
say "inside";
}
say "outside again";
# outside
# entered
# inside
# left
# outside again
The last value of an ENTER
phaser is returned so that it can be used in an expression.
Here's a bit of a contrived example:
# Raku
{
LEAVE say "stayed {now - ENTER now} seconds";
sleep 2;
}
# stayed 2.001867 seconds
The LEAVE
phaser corresponds to the "DEFER" functionality in many other modern programming languages.
KEEP & UNDO
The KEEP
and UNDO
phasers are special cases of the LEAVE
phaser. They are called depending on the return value of the surrounding block.
If the result of calling the defined
method on the return value is True
, then any KEEP
phasers will be called. If the result of calling the defined
method is not True, then any UNDO
phaser will be called. The actual value of the block will be available in the topic ($_
) inside the phaser.
.
A contrived example may clarify:
# Raku
for 42, Nil {
KEEP { say "Keeping because of $_" }
UNDO { say "Undoing because of $_.raku()" }
$_; # the return value of the block
}
# Keeping because of 42
# Undoing because of Nil
Maybe real-life example would be clearer:
# Raku
{
KEEP $dbh.commit;
UNDO $dbh.rollback;
… # set up a big transaction in a database
True; # indicate success
}
So, if anything goes wrong with setting up the big transaction in the database, the UNDO
phaser makes sure the transaction can be rolled back. Conversely, if the block is successfully left, the transaction will be automatically committed by the KEEP
phaser.
The KEEP
and UNDO
phasers give you the building blocks for a poor man's software transactional memory.
PRE & POST
The PRE
phaser is a special version of the ENTER
phaser. The POST
phaser is a special case of the LEAVE
phaser.
The PRE
phaser is expected to return a true value if it is ok to enter the block. If it does not, then an exception will be thrown. The POST
phaser receives the return value of the block and is expected to return a true value if it is ok to leave the block without throwing an exception.
An example with PRE
:
# Raku
{
PRE {
say "called PRE”;
False; # throws exception
}
…
}
say "we made it!"; # never makes it here
# called PRE
# Precondition '{ say "called PRE"; False }' failed
An example with PRE
and POST
:
# Raku
{
PRE {
say "called PRE";
True; # does NOT throw
}
POST {
say "called POST";
False; # throws exception
}
say "inside the block" # also returns True
}
say "we made it!"; # never makes it here
# called PRE
# inside the block
# called POST
# Postcondition '{ say "called POST"; False }' failed
If you just want to check if a block returns a specific value or type, you are probably better off specifying a return signature for the block. Note that:
# Raku
{
POST {
$_ ~~ Int; # check if return value is an Int
}
… # calculate result
$result;
}
is just a very roundabout way of saying:
# Raku
--> Int { # return value should be an Int
… # calculate result
$result;
}
In general, you would use a POST
phaser only if the necessary checks would be very involved and not reducible to a simple type check.
Loop phasers
Name Description
----------------------------------------------------------
FIRST Run before the first iteration
NEXT Run after each completed iteration, or with "next"
LAST Run after the last iteration, or with "last"
----------------------------------------------------------
Loop phasers are a special type of phaser specific to loop constructs. One is run before the first iteration (FIRST
), one is run after each iteration (NEXT
), and one is run after the last iteration (LAST
).
The names speak for themselves. A bit of a contrived example:
# Raku
my $total = 0;
for 1..5 {
$total += $_;
LAST say "------ +\n$total.fmt('%6d')";
FIRST say "values\n======";
NEXT say .fmt('%6d');
}
# values
# ======
# 1
# 2
# 3
# 4
# 5
# ------ +
# 15
Supported loop constructs include loop
, while
, until
, repeat
, for
, and map
and friends. You can use loop phasers with other block phasers if you want, but this is usually unnecessary.
Summary
In addition to the Perl special blocks that have counterparts in Raku (called phasers), Raku has a number of special-purpose phasers related to blocks of code and looping constructs.
The Raku Programming Language also has phasers related to exception handling and warnings, event-driven programming, and document (pod) parsing; these will be covered in separate blog posts.