Hack 47. Autodeclare Method Arguments


You know who you are. Stop repeating your $self.

Perl's object orientation is very flexible, in part because of its simplicity and minimalism. At times that's valuable: it allows hackers to build complex object systems from a few small features. The rest of the time it can be painful to do simple things.

Though not everyone always calls the invocant in methods $self, everyone has to declare and manage the invocant and other arguments. That's a bit of a dragbut it's fixable. Sure, you could use a full-blown source filter [Hack #94] to remove the need to shift off $self and process the rest of your argument list, but that's an unnecessarily large hammer to swing at such a small annoyance. There's another way.

The Hack

Solving this problem without source filters requires three ideas. First, there must be some way to mark a subroutine as a method, because not all subroutines are methods. Second, this should be compatible with strict, for good programming practices. Third, there should be some way to add the proper operations to populate $self and the other arguments.

The first is easy: how about a subroutine attribute [Hack #45] called Method? The third is also possible with a little bit of B::Deparse [Hack #56] and eval magic. The second is trickier....

A surprisingly short module can do all of this:

package Attribute::Method; use strict; use warnings; use B::Deparse; use Attribute::Handlers; my $deparse = B::Deparse->new( ); sub import {     my ( $class, @vars ) = @_;     my $package          = caller( );     my %references       =     (         '$' => \\undef,         '@' => [ ],         '%' => { },     );     push @vars, '$self';     for my $var (@vars)     {         my $reftype                 = substr( $var, 0, 1, '' );         no strict 'refs';         *{ $package . '::' . $var } = $references{$reftype};     } } sub UNIVERSAL::Method :ATTR(RAWDATA) {     my ($package, $symbol, $referent, undef, $arglist) = @_;     my $code                 = $deparse->coderef2text( $referent );     $code                    =~ s/{/sub {\\nmy (\\$self, $arglist) = \\@_;\\n/;     no warnings 'redefine';     *$symbol                 = eval "package $package; $code"; } 1;

All of the variables, including $self, have to be lexical within methods, lest bad things happen when calling one method from another, such as accidentally overwriting a global variable somewhere. The handler for the Method attribute takes the compiled code, deparses it, and inserts the sub keyword and the argument handling line before the rest of the code. All of the arguments to the attribute are the names of the lexical variables within the method.

Compiling that with eval produces a new anonymous subroutine, which the code then inserts into the symbol table after disabling the Subroutine %s redefined warnings.

Running the Hack

From any class in which you tire of declaring and fetching the same arguments over and over again, write instead:

package Easy::Class; use strict; use warnings; use Attribute::Method qw( $status ); sub new :Method {     bless { @_ }, $self; } sub set_status :Method( $status ) {     $self->{status} = $status; } sub get_status :Method {     return $self->{status}; } 1;

For every method marked with the :Method attribute, you get the $self invocant declared for free. For every method with that attribute parameterized with a list of variable names, you get those variables as well.

Notice the strange and deep magic in import( ) as well as the list of arguments passed to it; this is what bypasses the strict checking. If you use instead only the refs and subs strictures, you don't even have to pass the variables you want to Attribute::Method.

Hacking the Hack

Is this better than source filters? It's certainly not as syntactically tidy. On the other hand, attribute-based solutions are often less fragile than source filtering. In particular, they don't prevent the use of other source filters or other attributes. It also almost never failsif your subroutines have errors, Perl will report them when compiling from the point of view of the original code before even calling the attribute handler. This technique works best in classes with several methods that take the same arguments.

Another possible way to accomplish this task is to rewrite the optree of the code reference (with B::Generate and a lot of patience) to add the ops to assign the arguments to the proper variables. Of course, you'll also have to insert the lexical variables into the pad associated with the CV, but if you know what this means, you probably know how to do it.

Finding and fixing any lexicals that methods close over isn't as bad in comparison. See "Peek Inside Closures" [Hack #76].

See Ricardo Signes's Sub::MicroSig for an alternate approach to the same problem.




Perl Hacks
Perl Hacks: Tips & Tools for Programming, Debugging, and Surviving
ISBN: 0596526741
EAN: 2147483647
Year: 2004
Pages: 141

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net