Phasers
The lifetime (execution timeline) of a program is broken up into phases. A phaser is a block of code called during a specific execution phase.
Phasers
A phaser block is just a trait of the closure containing it, and is
automatically called at the appropriate moment. These auto-called blocks
are known as phasers, since they generally mark the transition from
one phase of computing to another. For instance, a CHECK
block is
called at the end of compiling a compilation unit. Other kinds of
phasers can be installed as well; these are automatically called at
various times as appropriate, and some of them respond to various
control exceptions and exit values. For instance, some phasers might be
called if the exit from a block is successful or not, with success in
this case defined by returning with a defined value or list without any
Failure or exception in the process.
Here is a summary:
BEGIN {...} # * at compile time, as soon as possible, only ever runs once
CHECK {...} # * at compile time, as late as possible, only ever runs once
INIT {...} # * at runtime, as soon as possible, only ever runs once
END {...} # at runtime, as late as possible, only ever runs once
DOC [BEGIN|CHECK|INIT] {...} # only in documentation mode
ENTER {...} # * at every block entry time, repeats on loop blocks.
LEAVE {...} # at every block exit time (even stack unwinds from exceptions)
KEEP {...} # at every successful block exit, part of LEAVE queue
UNDO {...} # at every unsuccessful block exit, part of LEAVE queue
FIRST {...} # at loop initialization time, before any ENTER
NEXT {...} # at loop continuation time, before any LEAVE
LAST {...} # at loop termination time, after any LEAVE
PRE {...} # assert precondition at every block entry, before ENTER
POST {...} # assert postcondition at every block exit, after LEAVE
CATCH {...} # catch exceptions, before LEAVE
CONTROL {...} # catch control exceptions, before LEAVE
LAST {...} # supply tapped by whenever-block is done, runs very last
QUIT {...} # catch async exceptions within a whenever-block, runs very last
COMPOSE {...} # when a role is composed into a class (Not yet implemented)
CLOSE {...} # appears in a supply block, called when the supply is closed
Phasers marked with a *
have a runtime value, and if evaluated
earlier than their surrounding expression, they simply save their result
for use in the expression later when the rest of the expression is
evaluated:
my $compiletime = BEGIN { now };
our $random = ENTER { rand };
As with other statement prefixes, these value-producing constructs may be placed in front of either a block or a statement:
my $compiletime = BEGIN now;
our $random = ENTER rand;
Most of these phasers will take either a block or a function reference. The statement form can be particularly useful to expose a lexically scoped declaration to the surrounding lexical scope without "trapping" it inside a block.
These declare the same variables with the same scope as the preceding example, but run the statements as a whole at the indicated time:
BEGIN my $compiletime = now;
ENTER our $random = rand;
(Note, however, that the value of a variable calculated at compile time may not persist under runtime cloning of any surrounding closure.)
Most of the non-value-producing phasers may also be so used:
END say my $accumulator;
Note, however, that
END say my $accumulator = 0;
sets the variable to 0 at END
time, since that is when the "my" declaration
is actually executed. Only argumentless phasers may use the statement form.
This means that CATCH
and CONTROL
always require a block, since they take
an argument that sets $_
to the current topic, so that the innards are able
to behave as a switch statement. (If bare statements were allowed, the
temporary binding of $_
would leak out past the end of the CATCH
or
CONTROL
, with unpredictable and quite possibly dire consequences. Exception
handlers are supposed to reduce uncertainty, not increase it.)
Some of these phasers also have corresponding traits that can be set on
variables; they use will
followed by the name of the phaser in lowercase.
These have the advantage of passing the variable in question into the closure as
its topic:
our $h will enter { .rememberit() } will undo { .forgetit() };
Only phasers that can occur multiple times within a block are eligible for this
per-variable form; this excludes CATCH
and others like CLOSE
or QUIT
.
The topic of the block outside a phaser is still available as OUTER::<$_>
.
Whether the return value is modifiable may be a policy of the phaser in
question. In particular, the return value should not be modified within a
POST
phaser, but a LEAVE
phaser could be more liberal.
Any phaser defined in the lexical scope of a method is a closure that closes
over self
as well as normal lexicals. (Or equivalently, an implementation
may simply turn all such phasers into submethods whose primed invocant is the
current object.)
When multiple phasers are scheduled to run at the same moment, the general tiebreaking principle is that initializing phasers execute in order declared, while finalizing phasers execute in the opposite order, because setup and teardown usually want to happen in the opposite order from each other.
Execution order
Compilation begins
BEGIN {...} # at compile time, As soon as possible, only ever runs once
CHECK {...} # at compile time, As late as possible, only ever runs once
COMPOSE {...} # when a role is composed into a class (Not yet implemented)
Execution begins
INIT {...} # at runtime, as soon as possible, only ever runs once
Before block execution begins
PRE {...} # assert precondition at every block entry, before ENTER
Loop execution begins
FIRST {...} # at loop initialization time, before any ENTER
Block execution begins
ENTER {...} # at every block entry time, repeats on loop blocks.
Exception maybe happens
CATCH {...} # catch exceptions, before LEAVE
CONTROL {...} # catch control exceptions, before LEAVE
End of loop, either continuing or finished
NEXT {...} # at loop continuation time, before any LEAVE
LAST {...} # at loop termination time, after any LEAVE
End of block
LEAVE {...} # when blocks exits, even stack unwinds from exceptions
KEEP {...} # at every successful block exit, part of LEAVE queue
UNDO {...} # at every unsuccessful block exit, part of LEAVE queue
Postcondition for block
POST {...} # assert postcondition at every block exit, after LEAVE
Async whenever-block is complete
LAST {...} # if ended normally with done, runs once after block
QUIT {...} # catch async exceptions
Program terminating
END {...} # at runtime, ALAP, only ever runs once
Program execution phasers
BEGIN
Runs at compile time, as soon as the code in the phaser has compiled, only runs once.
The return value is available for use in later phases:
say "About to print 3 things";
for ^3 {
say ^10 .pick ~ '-' ~ BEGIN { say "Generating BEGIN value"; ^10 .pick }
}
# OUTPUT:
# Generating BEGIN value
# About to print 3 things
# 3-3
# 4-3
# 6-3
The ^10 .pick
in the phaser is generated only once and is then re-used by the loop
during runtime. Note how the say in the BEGIN
block is executed before
the say that is above the loop.
CHECK
Runs at compile time, as late as possible, only runs once.
Can have a return value that is provided even in later phases.
Code that is generated at runtime can still fire off CHECK
and INIT
phasers, though of course those phasers can't do things that would require
travel back in time. You need a wormhole for that.
INIT
Runs after compilation during main execution, as soon as possible, only runs once. It can have a return value that is provided even in later phases.
When phasers are in different modules, the INIT
and END
phasers are
treated as if declared at use
time in the using module. (It is erroneous to
depend on this order if the module is used more than once, however, since the
phasers are only installed the first time they're noticed.)
Code that is generated at runtime can still fire off CHECK
and INIT
phasers, though of course those phasers can't do things that would require
travel back in time. You need a wormhole for that.
An INIT
only runs once for all copies of a cloned closure.
END
Runs after compilation during main execution, as late as possible, only runs once. It will close any open handles automatically.
When phasers are in different modules, the INIT
and END
phasers are
treated as if declared at use
time in the using module. (It is erroneous to
depend on this order if the module is used more than once, however, since the
phasers are only installed the first time they're noticed.)
Block phasers
Execution in the context of a block has its own phases.
Block-leaving phasers wait until the call stack is actually unwound to run. Unwinding happens only after some exception handler decides to handle the exception that way. That is, just because an exception is thrown past a stack frame does not mean we have officially left the block yet, since the exception might be resumable. In any case, exception handlers are specified to run within the dynamic scope of the failing code, whether or not the exception is resumable. The stack is unwound and the phasers are called only if an exception is not resumed.
These can occur multiple times within the block. So they aren't really traits,
exactly--they add themselves onto a list stored in the actual trait. If you
examine the ENTER
trait of a block, you'll find that it's really a list of
phasers rather than a single phaser.
All of these phaser blocks can see any previously declared lexical variables, even if those variables have not been elaborated yet when the closure is invoked (in which case the variables evaluate to an undefined value.)
ENTER
Runs at every block entry time, repeats on loop blocks.
Can have a return value that is provided even in later phases.
An exception thrown from an ENTER
phaser will abort the ENTER
queue, but
one thrown from a LEAVE
phaser will not.
LEAVE
Runs at every block exit time (even stack unwinds from exceptions), except when the program exits abruptly (e.g. with exit).
LEAVE
phasers for a given block are necessarily evaluated after any
CATCH
and CONTROL
phasers. This includes the LEAVE
variants, KEEP
and UNDO
. POST
phasers are evaluated after everything else, to guarantee
that even LEAVE
phasers can't violate postconditions.
An exception thrown from an ENTER
phaser will abort the ENTER
queue, but
one thrown from a LEAVE
phaser will not.
If a POST
fails or any kind of LEAVE
block throws an exception while the
stack is unwinding, the unwinding continues and collects exceptions to be
handled. When the unwinding is completed all new exceptions are thrown from
that point.
sub answer() {
LEAVE say „I say after the return value.“;
42 # this is the return value
}
Note: be mindful of LEAVE
phasers directly in blocks of routines, as
they will get executed even when an attempt to call the routine with wrong
arguments is made:
sub foo (Int) {
say "Hello!";
LEAVE say "oh noes!"
}
try foo rand; # OUTPUT: «oh noes!»
Although the subroutine's body did not get run, because the sub expects
an Int and rand returned a Num, its block was
entered and left (when param binding failed), and so the LEAVE
phaser
was run.
KEEP
Runs at every successful block exit, as part of the LEAVE
queue (shares the
same order of execution).
UNDO
Runs at every unsuccessful block exit, as part of the LEAVE
queue (shares the
same order of execution).
PRE
Asserts a precondition at every block entry. Runs before the ENTER
phase.
PRE
phasers fire off before any ENTER
or FIRST
.
The exceptions thrown by failing PRE
and POST
phasers cannot be caught by
a CATCH
in the same block, which implies that POST
phaser are not run if
a PRE
phaser fails.
POST
Asserts a postcondition at every block entry. Runs after the LEAVE
phase.
For phasers such as KEEP
and POST
that are run when exiting a scope
normally, the return value (if any) from that scope is available as the current
topic within the phaser.
The POST
block can be defined in one of two ways. Either the corresponding
POST
is defined as a separate phaser, in which case PRE
and POST
share
no lexical scope. Alternately, any PRE
phaser may define its corresponding
POST
as an embedded phaser block that closes over the lexical scope of the
PRE
.
If a POST
fails or any kind of LEAVE
block throws an exception while the
stack is unwinding, the unwinding continues and collects exceptions to be
handled. When the unwinding is completed all new exceptions are thrown from
that point.
The exceptions thrown by failing PRE
and POST
phasers cannot be caught by
a CATCH
in the same block, which implies that POST
phaser are not run if
a PRE
phaser fails.
Loop phasers
FIRST
, NEXT
, and LAST
are meaningful only within the lexical scope of
a loop, and may occur only at the top level of such a loop block.
FIRST
Runs at loop initialization, before ENTER
.
NEXT
Runs when loop is continued (either through next
or because you got to the
bottom of the loop and are looping back around), before LEAVE
.
A NEXT
executes only if the end of the loop block is reached normally, or an
explicit next
is executed. In distinction to LEAVE
phasers, a NEXT
phaser is not executed if the loop block is exited via any exception other than
the control exception thrown by next
. In particular, a last
bypasses
evaluation of NEXT
phasers.
LAST
Runs when a loop is finished because the condition is met, or when it exits
using last
; it is executed after LEAVE
.
Exception handling phasers
CATCH
Runs when an exception is raised by the current block, before the LEAVE
phase.
Also see Catching exceptions.
CONTROL
Runs when a control exception is raised by the current block, before the LEAVE
phase. It is raised by return
, fail
, redo
, next
, last
, done
, emit
,
take
, warn
, proceed
and succeed
.
say elems gather {
CONTROL {
when CX::Warn { say "WARNING!!! $_"; .resume }
when CX::Take { say "Don't take my stuff"; .resume }
when CX::Done { say "Done"; .resume }
}
warn 'people take stuff here';
take 'keys';
done;
}
# OUTPUT:
# WARNING!!! people take stuff here
# Don't take my stuff
# Done
# 0
Object phasers
COMPOSE (Not yet implemented)
Runs when a role is composed into a class.
Asynchronous phasers
LAST
Runs when a Supply finishes with a call to done
or when a
supply
block exits normally. It runs completely after the whenever
block
it is placed within finishes.
This phaser reuses the name LAST
, but works differently from the LAST
loop
phaser. This phaser is similar to setting the done
routine while tapping a
supply with tap
.
QUIT
Runs when a Supply terminates early with an exception. It runs
after the whenever
block it is placed within finishes.
This phaser is similar to setting the quit
routine while tapping a Supply
with tap
.
CLOSE
Appears in a supply block. Called when the supply is closed.
DOC phasers
DOC
The phasers BEGIN
, CHECK
and INIT
are run only in documentation mode
when prefixed with the DOC
keyword. The compiler is in documentation mode when run
with --doc
.
DOC INIT { say 'init' } # prints 'init' at initialization time when in documentation mode.