To see what is currently happening visit http://www.perl6.org/
Subroutines : Pre- and post- handlers for subroutines
Maintainer: Damian Conway <damian@conway.org> Date: 20 Sep 2000 Last Modified: 29 Sep 2000 Mailing List: perl6-language@perl.org Number: 271 Version: 3 Status: Frozen Frozen since: v2
In response to the desiderata set out in RFC 194 and in the Class::Contract module, this RFC proposes a generic "handler" mechanism that can install behaviours around (before or after) a subroutine invocation.
It is proposed to provide a mechanism which allows two sequences of "handlers" to be associated with a specific subroutine or built-in function (hereafter referred to as the primary).
One sequence of handlers (the prefix sequence) would be called
whenever the primary is invoked, but before the body of the primary
is executed. Each handler would itself be a subroutine, and each would
be called with the argument list being passed to the primary it prefixes,
plus an extra argument (specifically, $_[-1]) representing a slot for the
eventual return value of the primary. This last argument would normally
have the value undef.
The second sequence of handlers would be called after the body of the primary has executed, but before the return value is returned to the invoking scope. Once again, each handler would itself be a subroutine, and each would be called with the same argument list as the primary it postfixes, plus the "return value(s)" argument ($_[-1]). For a postfix handlers, this extra argument would hold a reference to an array containing the value(s) actually returned from its primary.
Normally prefix handlers would be prepended to the appropriate prefix sequence, whilst postfix handlers would be appended to the appropriate postfix sequence, thereby preserving the symmetry of pre- and post-fix handlers. The mechanisms for setting up such sequences are described in "General Syntax of Handler Installers".
A prefix handler may do anything that any other subroutine may do. A
typical action might be to trace or log the invocation of the primary
(the pre syntax is used to set up a handler, and is explained in
"General Syntax of Handler Installers").
package Foo;
# set up a tracing prefix handler for the subroutine &Foo::bar...
pre bar {
local $" = ','
my @caller = caller;
print "Called foo with args (@_[0..$#_-1])\n",
"from @caller[1,2]\n",
"in contexts: ", join(", ",want);
}
Note that this (correctly) implies that a handler receives the same
information from caller and want (RFC 21) as its primary would.
However, caller would be extended to return an extra value
when called from a prefix or postfix handler. Specifically, that extra
value would be a reference to the handler's primary. Thus a handler
could detect which primary it is prefixing (or postfixing):
sub handler {
my $primary = (caller(0))[10];
...
}
or (under RFC 259):
sub handler {
my $primary = caller->{primary};
...
}
Another common usage would be to acquire resources that the primary will use:
pre read_file {
flock $_[0], LOCK_EX;
}
(For the obvious complement of this usage, see "Postfix Handler Semantics").
Note that, in all cases, the return value of the handler is ignored.
Each handler receives the same argument list: the list with which the primary was originally called, plus an extra element representing the return value.
If a handler changes one of the original arguments -- through one of the aliases in its @_ array, or by splicing @_ itself -- those changes are passed on to subsequent handlers and to the primary itself. For example:
pre tax_payable_on {
$_[0] -= 20.00; # routinely underquote sales price
}
sub tax_payable_on { # now sees prices $20 less than
# specified argument
printf("Tax: %.2lf", $_[0] * 0.1);
}
$price = 99.95; tax_payable_on($price); # prints 8.00
$price = 29.95; tax_payable_on($price); # prints 0.99
$price = 9.95; tax_payable_on($price); # prints -1.01 (a profit!)
Changes to individual elements of @_ would also be propagated back to the original argument (as usual).
Note that it would still be possible to set up a handler that changes its primary's argument list but doesn't propagate the changes back to the original arguments.
For example, the following prefix handler allows
CORE::open to handle URLs, but changes the second argument:
use Regexp::Common;
pre CORE::open {
$_[1] = "lynx -source $_[1] |"
if $_[1] =~ $RE{URL};
}
On the other hand, the following handler does exactly the same thing without propagating the changed filename back to the original second argument:
pre CORE::open {
splice @_, 1, 1, "lynx -source $_[1] |"
if $_[1] =~ $RE{URL};
}
Note that splicing @_ is also the mechanism for altering the number
of arguments passed to the primary. For example, the following
prefix handler changes the semantics of CORE::open when called
with only one argument:
pre CORE::open {
splice @_, $#_, 0, $_
if @_ == 2;
}
With this handler in effect, a call to open SOMEFILEHANDLE; will look
for the filename in $_ instead of $SOMEFILEHANDLE.
Note that, in the above example, it is important to splice the extra argument before index $#_ (rather than just pushing the extra argument onto the end of @_), because the last element of @_ is always the return value.
The ability to rearrange arguments in this way opens the door to some of
the intriguing possibilities suggested by the Lingua::Romana::Perligata
module. For example, here's how to make bless's arguments
order-independent:
pre CORE::bless {
splice @_, 0, 2, reverse @_[0..1]
if !ref($_[0]) && ref($_[1]);
}
# and later...
$obj = bless 'ClassName', { attr => $val };
If a handler assigns the value to the return value slot,
either explicitly ($_[-1] = $newval) or implicitly
(splice @_, -1, 1, $newval), then the remaining prefix and postfix
handlers are still called, but the primary itself is not called.
Instead, the value in $_[-1] is used as the return value for the primary. This allows short-circuiting techniques such as memoization to be easily implemented:
my %sin_cache;
pre CORE::sin {
$_[-1] = $sin_cache{$_[0]}; # short-circuit
if exists $sin_cache{$_[0]} # if value cached
}
post CORE::sin {
$sin_cache{$_[0]} = $_[-1]; # cache the return val
}
This feature might also be used to short-circuit upon failure to acquire resources:
pre read_file {
flock($_[0], LOCK_EX|LOCK_NB)
or $_[-1] = undef; # Can't lock file so...
# "No data for you!"
}
Note that this second example (correctly) implies that short-circuiting occurs as a result of an assignment to $_[-1], even if that assignment doesn't change the value of $_[-1].
If a handler throws an exception, that exception is immediately propagated back to the calling scope, without having invoked any other handlers (nor the primary itself). This is useful for setting up preconditions on subroutine and methods (for Design-By-Contact programming). For example:
package PriorityList;
pre pop {
my ($self) = @_;
croak "Can't pop empty PriorityList"
unless @{$self->{list}} > 0;
}
An exception might also be an appropriate response on resource acquisition failure:
pre read_file {
flock($_[0], LOCK_EX|LOCK_NB) or
croak "Couldn't get immediate lock";
}
When called, a postfix handlers may do anything that any other subroutine may do. Just like a prefix handler, it may print out tracing information:
package Foo;
post bar {
local $" = ','
my @caller = caller;
print "Finished call to foo from @caller[1,2]\n",
"returning: @{$_[-1]}";
}
or release resources:
post read_file {
flock $_[0], LOCK_UN;
}
or alter the primary's argument list (for example: to effect changes to the original values after the primary has finished with them):
post issue_licence {
$_[0]--; # decrement licence counter
carp "You just used your last licence"
if $_[0] == 0;
}
or change the return value:
post tax_payable_on {
$_[-1] -= 1.00; # routinely underquote tax payable
}
sub tax_payable_on {
printf("Tax: %.2lf", $_[0] * 0.1);
}
tax_payable_on(99.95); # prints 9.00
tax_payable_on(29.95); # prints 2.00
tax_payable_on( 9.95); # prints -0.01
or throw an exception (for example, to implement method post-conditions in a DBC system):
package PriorityList;
post pop {
my ($self, $return_val) = @_;
croak "pop returned undef (something odd is happening)"
unless defined $return_val;
}
It is proposed that two built-in functions -- pre and post -- be
introduced. These may be used to install prefix or postfix handlers for
any subroutine (or for all subroutines in a single package).
Each would take two arguments:
Either a subroutine name (possibly as a bareword), or a subroutine reference, or a class name (possibly as a bareword). This argument would be mandatory.
Either a block or subroutine reference implementing the handler, or a reference to a hash whose single value is a sub reference implementing the handler, or a string or bareword specifying the logical name of a handler. This argument would be optional.
Thus, in the notation proposed by RFC 128:
pre ( [""&]identifier; [""&\