NAME
Syntax::Keyword::Finally - add FINALLY phaser block syntax to perl
SYNOPSIS
use Syntax::Keyword::Finally;
{
my $dbh = DBI->connect( ... ) or die "Cannot connect";
FINALLY { $dbh->disconnect; }
my $sth = $dbh->prepare( ... ) or die "Cannot prepare";
FINALLY { $sth->finish; }
...
}
Also available as a keyword spelled defer
use Syntax::Keyword::Defer;
{
my $dbh = DBI->connect( ... ) or die "Cannot connect";
defer { $dbh->disconnect; }
my $sth = $dbh->prepare( ... ) or die "Cannot prepare";
defer { $sth->finish; }
...
}
DESCRIPTION
This module provides a syntax plugin that implements a phaser block
that executes its block when the containing scope has finished. The
syntax of the FINALLY block looks similar to other phasers in perl
(such as BEGIN), but the semantics of its execution are different.
The defer alias is identical in syntax and semantics, just spelled
differently. It is provided as an alternative experiment, in order to
look identical to similar features provided by other languages (Swift,
Zig, Jai, Nim and Odin all provide this). Note that while Go also
provides a defer keyword, the semantics here are not the same. Go's
version defers until the end of the entire function, rather than the
closest enclosing scope as is common to most other languages, and this
module.
The operation can be considered a little similar to an END block, but
with the following key differences:
* A FINALLY block runs at the time that execution leaves the block it
is declared inside, whereas an END block runs at the end time of the
entire program regardless of its location.
* A FINALLY block is invoked at the time its containing scope has
finished, which means it might run again if the block is entered
again later in the program. An END block will only ever run once.
* A FINALLY block will only take effect if execution reaches the line
it is declared on; if the line is not reached then nothing happens.
An END block will always be invoked once declared, regardless of the
dynamic extent of execution at runtime.
FINALLY blocks are primarily intended for cases such as resource
finalisation tasks that may be conditionally required.
For example in the synopsis code, after normal execution the statement
handle will be finished using the $sth->finish method, then the
database will be disconnected with $dbh->disconnect. If instead the
prepare method failed then the database will still be disconnected, but
there is no need to finish with the statement handle as the second
FINALLY block was never encountered.
KEYWORDS
FINALLY
FINALLY {
STATEMENTS...
}
The FINALLY keyword introduces a phaser block (similar to e.g. BEGIN
and END), which runs its code body at the time that its immediately
surrounding code block finishes.
When the FINALLY statement is encountered, the body of the code block
is pushed to a queue of pending operations, which is then flushed when
the surrounding block finishes for any reason - either by implicit
fallthrough, or explicit termination by return, die or any of the loop
control statements next, last or redo.
sub f
{
FINALLY { say "The function has now returned"; }
return 123;
}
If multiple FINALLY statements appear within the same block, they are
pushed to the queue in LIFO order; the last one encountered is the
first one to be executed.
{
FINALLY { say "This happens second"; }
FINALLY { say "This happens first"; }
}
A FINALLY phaser will only take effect if the statement itself is
actually encountered during normal execution. This is in direct
contrast to an END phaser which always occurs. This makes it ideal for
handling finalisation of a resource which was created on a nearby
previous line, where the code to create it might have thrown an
exception instead. Because the exception skipped over the FINALLY
statement, the code body does not need to run.
my $resource = Resource->open( ... );
FINALLY { $resource->close; }
Unlike as would happen with e.g. a DESTROY method on a guard object,
any exceptions thrown from a FINALLY block are still propagated up to
the caller in the usual way.
use Syntax::Keyword::Finally;
sub f
{
my $count = 0;
FINALLY { $count or die "Failed to increment count"; }
# some code here
}
f();
$ perl example.pl
Failed to increment count at examples.pl line 6.
Because a FINALLY block is a true block (e.g. in the same way something
like an if () {...} block is), rather than an anonymous sub, it does
not appear to caller() or other stack-inspection tricks. This is useful
for calling croak(), for example.
sub g
{
my $count = 0;
FINALLY { $count or croak "Expected some items"; }
$count++ for @_;
}
Here, croak() will correctly report the caller of the g() function,
rather than appearing to be called from an __ANON__ sub invoked at the
end of the function itself.
TODO
This module contains a unit test file copied and edited from my core
perl branch to provide the same syntax. Several test cases are
currently commented out because this implementation does not yet handle
them:
* Try to fix the double-exception test failure on Perl versions
before v5.20. (Test currently skipped on those versions)
* Permit the use of goto or next/last/redo within FINALLY blocks,
provided it does not jump to a target outside.
E.g. the following ought to be permitted, but currently is not:
FINALLY {
foreach my $item (@items) {
$item > 5 or next;
...
}
}
* Try to detect and forbid nonlocal flow control (goto,
next/last/redo) from leaving the FINALLY block.
E.g. currently the following will crash the interpreter:
sub func { last ITEM }
ITEM: foreach(1..10) {
say;
defer { func() }
}
AUTHOR
Paul Evans <
[email protected]>