The AOP people have this vague notion of "crosscutting concerns," which are things that are not easily modularizeable, because they touch many disperate parts of your program.

The canonical example is logging. Say you want to trace your program's execution. You might begin every sub with something like:

sub foo { log_msg("In subroutine foo") if $DEBUG }

That's a lot of extra typing and redundant code to look at. Good programmers fall violently ill when exposed to redundant code, so the AOP people came up with lots of fancy buzzwords and neato Java stuff to over-engineer a solution: AspectJ. (There's also a very good Perl version.)

But I'm a Perl guy and I don't like all this ivory-tower Javesque bloatism, so I decided to check out what the deal is with these fancy new subroutine attributes. Here's what I came up with.

package Foo; use strict; use warnings; use Attribute::Handlers; sub _log : ATTR(CODE) { my ($package, $symbol) = @_; { no strict 'refs'; no warnings 'redefine'; my $name = *{ $symbol }{NAME}; my $code = \&{ $package . '::' . $name }; *{ $package . '::' . $name } = sub { my $self = shift; print "Entering sub $name\n"; $code->( $self, @_ ); }; } } sub foo : _log { my $self = shift; print "in foo\n"; } 1;

So now I can run perl -MFoo -e 'Foo->foo' and I get:

Entering sub foo in foo

That's just a simple proof-of-concept. Logging isn't really something that's so complicated that it can't be handled in simpler ways. But I think I am going to use this model to associate authorization calls with CGI::Application runmode methods in an app I'm currently working on. Then I can write:

sub rm_secret_stuff : _authz(admin) { ... }

Which would wrap the sub and call $self->authz('admin')->authorize to check if you have permission for that runmode.

Good idea? Bad idea? Other suggestions are always welcome.

Replies are listed 'Best First'.
Re: Subroutine attributes for solving crosscutting problems
by chromatic (Archbishop) on Sep 08, 2006 at 00:49 UTC

    Good idea. It would be even nicer if it were easier to splice optrees for subs together, but that requires cloning and that's a bit tricky.

    I do have one suggestion. You don't have to go through that symbolic reference mess in your handler; the third argument is the referent itself -- the coderef here.

      You don't have to go through that symbolic reference mess in your handler; the third argument is the referent itself -- the coderef here.

      Hah. Whenever I learn something new I always manage to overlook one obvious detail. :) Thanks for the idea.
Re: Subroutine attributes for solving crosscutting problems
by Jenda (Abbot) on Sep 08, 2006 at 08:30 UTC

    For reference, regarding the logging: there is a CPAN module, Devel::TraceSubs, that allows you to attach logging to all/some subroutines in your program without any need to touch them. So you can trace even subroutines in third party modules etc.

Re: Subroutine attributes for solving crosscutting problems
by bennymack (Pilgrim) on Sep 08, 2006 at 00:58 UTC

    That's a neat way to do it. I've been kicking around the idea of using attributes in some of my programming but just haven't found a situation where they're the best solution yet.

    It's not demonstrated in your example here but something that would be a nice additional feature would be to allow compile time enabling/disabling of the effects of the attribute. In this case, logging.

    Ideally this could be used as a way to turn logging on and off based on some condition or configuration so the transition from development to production would require no code changes logging wise.

    Lastly, I don't think you need to shift $self then hand it back to the code ref call. Just do $code->( @_ ) with the whole stack.

Re: Subroutine attributes for solving crosscutting problems
by radiantmatrix (Parson) on Sep 11, 2006 at 14:29 UTC

    I've always just abstracted general-purpose code like this into a utility module (or modules, as makes sense). For example, I use an updated version of the module in RFC: Log::Painless to handle common logging, and subs look like this:

    sub foo { trace enter; #logs entry into the sub if loglevel is 'trace' eval { sub_that_might_die(shift @_); }; if ($@) { debug caught; #logs catching of exception if loglevel is 'debug' warn $@; return; } trace leave; #logs departure from sub if loglevel is 'trace' }

    Yes, it's still repetitive to type trace enter at the top of each sub, but no more so than using attributes, and I find it's very natural. I don't know if a similar approach would address all your "cross-cutting concerns", but I thought I'd throw it out there as a possibility.

    Good luck... and let us know what you decide and why, I'm curious. ;-)

    <radiant.matrix>
    A collection of thoughts and links from the minds of geeks
    The Code that can be seen is not the true Code
    I haven't found a problem yet that can't be solved by a well-placed trebuchet
Re: Subroutine attributes for solving crosscutting problems
by diotalevi (Canon) on Sep 08, 2006 at 18:00 UTC

    The lisp people call this "advice" that you apply to code by "advising" it, FYI.

    ⠤⠤ ⠙⠊⠕⠞⠁⠇⠑⠧⠊