DEV Community

Cover image for Phasers (Part 2 of 2)
Elizabeth Mattijsen
Elizabeth Mattijsen

Posted on • Edited on

Phasers (Part 2 of 2)

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 fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

is just a very roundabout way of saying:

# Raku
--> Int {  # return value should be an Int
   …      # calculate result
   $result;
}
Enter fullscreen mode Exit fullscreen mode

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"
  ----------------------------------------------------------
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Supported loop constructs include loop, whileuntil, 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.

Top comments (0)