tags:

views:

229

answers:

5

Can you intercept a method call in Perl, do something with the arguments, and then execute it?

+6  A: 

This looks like a job for Moose! Moose is an object system for Perl that can do that and lots more. The docs will do a much better job at explaining than I can, but what you'll likely want is a Method Modifier, specifically before.

wes
Moose is something you use for the entire application, not targeted problems.
brian d foy
But it depends on what the problem is, and the asker of the question doesn't state any details. It might be someone evaluating an approach to take when creating a new application in which case being turned on to Moose would be an appropriate and beneficial response.
jsoverson
We just said the same thing. I didn't say not to use Moose, but I didn't say to use it either.
brian d foy
Moose kicks some serious ass
xxxxxxx
+6  A: 

To describe briefly, Perl has the aptitude to modify symbol table. You call a subroutine (method) via symbol table of the package, to which the method belongs. If you modify the symbol table (and this is not considered very dirty), you can substitute most method calls with calling the other methods you specify. This demonstrates the approach:

# The subroutine we'll interrupt calls to
sub call_me
{
    print shift,"\n";
}

# Intercepting factory
sub aspectate
{
    my $callee = shift;
    my $value = shift;
    return sub { $callee->($value + shift); };
}
my $aspectated_call_me = aspectate \&call_me, 100;

# Rewrite symbol table of main package (lasts to the end of the block).
# Replace "main" with the name of the package (class) you're intercepting
local *main::call_me = $aspectated_call_me;

# Voila!  Prints 105!
call_me(5);

This also shows that, once someone takes reference of the subroutine and calls it via the reference, you can no longer influence such calls.

I am pretty sure there are frameworks to do aspectation in perl, but this, I hope, demonstrates the approach.

Pavel Shved
+10  A: 

Yes, you can intercept Perl method calls. I have an entire chapter about that sort of thing in Mastering Perl.

Check out the Hook::LexWrap module, which lets you do it without going through all of the details.

brian d foy
+3  A: 

Yes.

You need three things:

The arguments to a call are in @_ which is just another dynamically scoped variable.

Then, goto supports a reference-sub argument which preserves the current @_ but makes another (tail) function call.

Finally local can be used to create lexically scoped global variables, and the symbol tables are buried in %::.

So you've got:

sub foo {
    my($x,$y)=(@_);
    print "$x / $y = " . ((0.0+$x)/$y)."\n";
}
sub doit {
    foo(3,4);
}
doit();

which of course prints out:

3 / 4 = 0.75

We can replace foo using local and go:

my $oldfoo = \&foo;
local *foo = sub { (@_)=($_[1], $_[0]); goto $oldfoo; };
doit();

And now we get:

4 / 3 = 1.33333333333333

If you wanted to modify *foo without using its name, and you didn't want to use eval, then you could modify it by manipulating %::, for example:

$::{"foo"} = sub { (@_)=($_[0], 1); goto $oldfoo; };
doit();

And now we get:

3 / 1 = 3
geocar
+5  A: 

You can, and Pavel describes a good way to do it, but you should probably elaborate as to why you are wanting to do this in the first place.

If you're looking for advanced ways of intercepting calls to arbitrary subroutines, then fiddling with symbol tables will work for you, but if you want to be adding functionality to functions perhaps exported to the namespace you are currently working in, then you might need to know of ways to call functions that exist in other namespaces.

Data::Dumper, for example, normally exports the function 'Dumper' to the calling namespace, but you can override or disable that and provide your own Dumper function which then calls the original by way of the fully qualified name.

e.g.

use Data::Dumper;

sub Dumper {
   warn 'Dumping variables';
   print Data::Dumper::Dumper(@_);
}

my $foo = {
   bar   => 'barval',
};

Dumper($foo);

Again, this is an alternate solution that may be more appropriate depending on the original problem. A lot of fun can be had when playing with the symbol table, but it may be overkill and could lead to hard to maintain code if you don't need it.

jsoverson
In this case, you should give an empty import list to Dumper so you don't import anything. Also, sequence matters in this case. You have to import first, then override. The last subroutine definition wins. Finally, under warnings, you'll get a 'redefine' error doing this.
brian d foy
Definitely, just trying to keep it simple for the sake of example. Being direct has more value (and is quicker for the responder) than explaining every tangential detail. The concepts of importing and warnings would be best explained as other questions anyway.
jsoverson