This is part three in the "Cases of UPPER" series of blog posts, describing the Raku syntax elements that are completely in UPPERCASE.
The first two parts discussed the four phasers linked to execution stages of the Raku Programming Language: BEGIN, CHECK, INIT and END.
This part will discuss the phasers related to scopes (aka, the code between curly braces): ENTER and LEAVE and their special versions.
Scope counterparts
The ENTER and LEAVE phasers are the "scope" counterparts of the INIT and END phasers. A simple but contrived example:
if 42 {
LEAVE say "goodbye";
ENTER say "hello";
}
will show:
hello
goodbye
as one might have expected.
However there are subtle difference in behaviour between the LEAVE and END phaser with regards to exit:
$ raku -e 'LEAVE say "goodbye"'
goodbye
$ raku -e 'LEAVE say "goodbye"; die "urgh"'
goodbye
urgh
in block <unit> at -e line 1
$ raku -e 'LEAVE say "goodbye"; exit 42'
$ echo $?
42
Note that exit will not execute the LEAVE phaser. But leaving a scope normally or with an execution error will execute the LEAVE phaser.
One may have noticed that there are no scopes mentioned (
{ }) in these examples. That's because the mainline of any Raku program is an implicit scope. You can think of the source of a Raku program to be between curly braces, and followed by anexit:{ your program }; exit.
As with INIT, the ENTER phasers are executed in the order they're seen. And the LEAVE phasers are executed in the reverse order.
$ raku -e 'ENTER say 1; ENTER say 2'
1
2
$ raku -e 'LEAVE say 1; LEAVE say 2'
2
1
Although it must be said that rarely does one need to have multiple ENTER or LEAVE phasers in the same scope.
As with the INIT phaser, the ENTER phaser returns the value last seen. So you can also use this for timing spent in a scope:
sub frobnicate() {
LEAVE say "Frobnicated for { now - ENTER now } seconds.";
sleep .5; # mimic activity
}
frobnicate;
This would show something like:
Frobnicated for 0.503981515 seconds.
Pre and Post Conditions
The PRE and POST phasers are special cases of the ENTER and LEAVE phasers. The code in the PRE and POST phasers are supposed to produce a True or False value. If they produce a False value, then an execution error will occur stating the condition.
Maybe an (again contrived) example is easier to grasp:
sub process($io) {
PRE $io ~~ IO && $io.defined;
42
}
say process("foo".IO); # 42
say process(666); # Precondition '$io ~~ IO && $io.defined' failed
Note that the first invocation of process ran ok (because it referred to the current directory, and that is always there). The second one invocation failed because the argument given was not an object doing the IO role.
Note that a more idiomatic way would have been an
IO:Dconstraint on the$ioparameter.
Similarly with POST:
sub process(IO:D $io) {
POST $io.e;
42
}
say process(".".IO); # 42
say process("foo".IO); # Postcondition '$io.e' failed
Note again that the first invocation ran ok, and the second one failed because the argment given did not refer to an existing path (presumably, unless you had a file "foo" in the current directory).
Looking at the usage of
PREandPOSTphasers in the ecosystem, it doesn't look like there is a lot use made of this feature. Implementation of these phasers predated multi-dispatch and signature checking. So maybe these phasers are not very useful anymore. But they are still kept for backward compatibility.
Commit and Rollback
No, the Raku Programming Language does not have Software Transactional Memory as such. But it has two phasers that would allow one to get pretty close: KEEP and UNDO (which are special cases of LEAVE phasers).
If a block has a KEEP or UNDO phaser specified, one or the other will be executed depending on whether leaving the block was considered successful. Two criteria are applied:
- was the block exited normally (without a raised exception)?
- does
.definedproduceTrueorFalseon the return value of the block?
If both criteria are True, then any KEEP phaser will be executed. If either was False, then the UNDO phaser will be executed. This feature could e.g. be used to commit or rollback a database statement.
sub frobnicate() {
KEEP say "commit database transaction";
UNDO say "rollback database transaction";
Bool.roll # randomly succeed / fail
}
frobnicate(); # commit database transaction
frobnicate(); # rollback database transaction
Or do something else entirely. It's hard to come up with short examples that aren't contrived.
The
KEEPandUNDOphasers are used by some very prominent modules in the Raku Ecosystem, such aszef,RedandNet::BGP. So they have definitely proven their worth!
Queues
Each block has a queue for ENTER-like phasers (ENTER, PRE, FIRST) and LEAVE-like phasers (LEAVE, POST, KEEP, UNDO, NEXT and LAST). The ENTER-like phasers are executed in the order they are seen. The LEAVE-like phasers are executed (if appropriate) in the reverse order they are seen.
The FIRST, NEXT and LAST phasers will be discussed in the next episode.
Conclusion
The ENTER and LEAVE phasers apply to the scope in which they occur, and can be used for resource management and performance logging. Just like INIT and END, but with a finer scope.
The KEEP and UNDO phasers are a special case of a LEAVE phaser executed depending on whether the scope in which they occur is left successfully (KEEP) or unsuccessfully (UNDO).
The PRE and POST phaser provide gate-keeping functions for scopes, but have lost a lot of their usefulness because of other ways of gate-keeping scopes using signatures.
It doesn't matter where the phasers are specified in the code they belong to: they will be executed at the expected moment and with the scope in which they are specified.
This concludes the third episode of cases of UPPER language elements in the Raku Programming Language. Stay tuned for more!
Top comments (0)