This file is part of the Perl 6 Archive

To see what is currently happening visit http://www.perl6.org/

TITLE

Subroutines : Pre- and post- handlers for subroutines

VERSION

  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

ABSTRACT

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.

DESCRIPTION

Overall semantics

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".

Prefix Handler Semantics

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.

Special semantics: changes to arguments

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 };

Special semantics: changes to return value slot

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].

Special semantics: exceptions

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";
        }

Postfix Handler Semantics

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;
        }

General Syntax of Handler Installers

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:

Thus, in the notation proposed by RFC 128:

        pre  ( [""&]identifier; [""&\