Aspect.pm 1.00 released, and a first look at writing Aspect code
I'm happy to report that version 1.00 of Aspect.pm has been uploaded to CPAN, and so Perl's Aspect-Oriented Programming toolkit is now officially done.
http://svn.ali.as/cpan/releases/Aspect-1.00.tar.gz
The API should remain stable for some time (until the notional 2.00 with it's major rewrite into opcode manipulating XS). Most future changes in the 1.00 series should comprise additions of keywords or features, and code written now should be forward compatible.
But what is Aspect-Oriented Programming, and what it is good for? The standard documentation in AspectJ is full of jargon and most of the code samples are notional at best, and make it hard to connect to any real world situations.
The simplest way to describe the Perl Aspect.pm is that it provides a way of hijacking functions or methods with very little code, and in a very precise and controlled manner.
Since you can hijack basically any module loaded in your program, in production code the use of Aspect-Oriented Programming inevitably runs into the argument on the pros and (significant) negatives of what the Ruby folks call "Monkey Patching". Hijacking some deep core library to add a convenience feature for yourself, which will inevitably explode violently later when you try to add some other library that uses it as well to your program.
So it is with some reluctance that I tell you that Aspect.pm is unfreakingbelievably good for Monkey Patching. Now don't do it. Seriously.
But this kind of jiggery pokery is encouraged in a few specific areas. Namely development and testing tools. Tracing is a great example.
Lets say I've written a module Foo::Bar (and half a dozen child modules as well) and I want to understand the code flow through this module. The code isn't particularly magic, I just want some idea of how the code flows for a particular test case.
So lets write some tracing code to work it out. I want something like DProf's tracing mode, which shows call depth, but I only care about my module. I don't want to have to dig through an entire massive dump produced by something like dprofpp -T.
my $depth = 0; around { $depth++; print STDERR ' ' x $depth . $_->sub_name . "\n"; $_->proceed; $depth-- } call qr/^Foo::Bar::/;
And that's it, the entire tracing implementation with one variable and one expression.
If you run that Aspect code and then call your module as normal, in additional to whatever your program already does, you also get a nice printout of the functions that were called in any class in your modules nested by call depth.
It's also great for assertions during development.
Lets say I have a 300 class tree of WorkThing:: modules, which we've written in typical corporate get_thing, set_thing style. And I'd like to do a check for any get() or get_*() method in my entire giant tree that is being called in void context (because it indicates a likely bug).
A more substantial and sophisticated usage like the following would be completely reasonable.
my %seen = (); before { my $name = $_->sub_name; unless ( $seen{$name}++ ) { warn "Getter method $name called in void context\n"; } } call qr/WorkThing\b::get_?\w+$/ & wantvoid & highest;
In this example, we first identify all of the getter methods in our source tree. We then limit the assertion to only calls made to this function in void context (where wantarray is undef).
We then limit to just this highest occurance in any call stack, to prevent double positives from things like getFoo { $_[0]->getBar } where the lower call merely inherited the void context from the upper one.
We then do flood protection in the advice code itself, only warning once for each unique subroutine that was called.
As you can see, it's fairly easy to start stacking up conditionals in complicated and interesting ways. And while the speed penalty of something this high frequency will start to mount fairly quickly, we're not going to be running this in production so it's not a big problem.
Once you have a neat little toy like this, it's also fairly easy to package up into a reusable module. The tracing example above is already pre-packaged as a CPAN module Aspect::Library::Trace.
And with the addition of some import-time hackery, the example above can be compressed even further to the following single line.
use Aspect::Library::Trace qr/^Foo::Bar::/;
Very quick to use, and extremely handy.
What is a good resource for reading up on Aspect Oriented Programming?
Wikipedia is a reasonable start
I'm very happy to see there are good recommendations in here. In particular, I've seen awful AOP examples where the code cannot function properly without aspects and this can lead to many subtle bugs.
Your examples are clear and well thought out.
Ovid: Because Perl has a history of doing evil symbol table hackery, we have a history of failures in this area that help us to understand why screwing with other people's modules are a bad idea.
My hope is that because of this, most of the things Perl people will think are good places for Aspect.pm are much more sane than the places Java people think are useful.
And I'm not planning at the moment to implement some of the stuff in AspectJ that basically formalises @ISA manipulation as a legitimate programming technique.
Thank you for your continuing work on Aspect.pm!
You might also like "dip" - Dynamic Instrumentation in Perl - a bit like DTrace, using aspects:
http://search.cpan.org/perldoc?dip
Marcel: dip is pretty interesting. There's a sub-optimal example in your module though, where you hook the same function twice (once with before, once with after).
That is going to result in two levels of function wrapping. Switching to an around { } advice would let you have only a single level of aspect wrapping on the function and lets you use local variables for state storage between the beginning and end.