views:

760

answers:

3

A little known built-in Perl feature is attributes. However, the official documentation is doing a rather bad job introducing newbies to the concept. At the same time, frameworks like Catalyst use attributes extensively which seems to make many things easier there. Since using something without knowing the implications sucks a bit, I'd like to know the details. Syntax-wise they look like Python's decorators, but the documentation implies something simpler.

Could you explain (with real-world examples if possible) what attributes are good for and what happens behind the doors?

A: 

Attributes are one of the things that if you don't know how to use them, you shouldn't bother with them. I once made a database_method attribute, to indicate to the system that a record set would be requested before entering this method and that the method knew it's main inputs would come from the stored procedure it corresponded to.

I was using attributes to wrap the actual, specified actions with that data. So one of the really seemingly useful ideas is to wrap methods with indirection, but it was harder to make caller work, without overriding it. In the end it was much too visible as an "expert-only" feature and would have required support to trace through the arcane innards--something you want to avoid, if you write Perl in a perl-also shop.


People may want to vote me down, but I take from the article cited by Sinan:

Caveats

Although this is a powerful technique, it isn't perfect. The code will not properly wrap anonymous subroutines, and it won't necessarily propagate calling context to the wrapped functions. Further, using this technique will significantly increase the number of subroutine dispatches that your program must execute during runtime. Depending on your program's complexity, this may significantly increase the size of your call stack. If blinding speed is a major design goal, this strategy may not be for you.

These are significant drawbacks unless you're willing to override caller. I don't care about "blinding speed" quite as much, and I'm half-willing to try my hand at overriding caller to bypass any subroutine that registers itself as "DO_NOT_REPORT" -- but I have some coding foolhardiness that hasn't yet been beaten out of me, too.

Even the article admits how ill-documented this feature is, and contains this caveat. Tell me when else it has been a good idea to use a snazzy, obscure feature? That often enough, people end up putting in the UNIVERSAL namespace to avoid the inheritance issue.

(But if you do think it's a bad answer, just another downvote will give me a peer pressure badge :D)

Axeman
True, but these caveats apply only to this particular way of using attributes, which is to wrap the original method. In most cases where attributes are used (Catalyst, etc.), they are used simply for tagging (I think), which is not problematic at all.
trendels
Still though this is given as one of the chief uses in one of the clearest Perl tutorials (one that would have saved me some time.) on the subject. I have to admit that I haven't checked in to Catalyst quite yet.
Axeman
+7  A: 

$perl->buzz recommends Mike Friedman's article.

Sinan Ünür
@brian Thank you for the correction. I am going to blame selective perception for picking only your name out of the list of authors on that page ;-) Now that I look at it again, it is obvious Mike Friedman is the author.
Sinan Ünür
+14  A: 

You are right, the documentation is not very clear in this area, especially since attributes are not so complicated. If you define a subroutine attribute, like this:

sub some_method :Foo { }

Perl will while compiling your program (this is important) look for the magic sub MODIFY_CODE_ATTRIBUTES in the current package or any of its parent classes. This will be called with the name of the current package, a reference to your subroutine, and a list of the attributes defined for this subroutine. If this handler does not exist, compilation will fail.

What you do in this handler is entirely up to you. Yes, that's right. No hidden magic whatsoever. If you want to signal an error, returning the name of the offending attributes will cause the compilation to fail with an "invalid attribute" message.

There is another handler called FETCH_CODE_ATTRIBUTES that will be called whenever someone says

use attributes;
my @attrs = attributes::get(\&some_method);

This handler gets passed the package name and subroutine reference, and is supposed to return a list of the subroutine's attributes (though what you really do is again up to you).

Here is an example to enable simple "tagging" of methods with arbitrary attributes, which you can query later:

package MyClass;
use Scalar::Util qw( refaddr );

my %attrs; # package variable to store attribute lists by coderef address

sub MODIFY_CODE_ATTRIBUTES {
    my ($package, $subref, @attrs) = @_;
    $attrs{ refaddr $subref } = \@attrs;
    return;
}

sub FETCH_CODE_ATTRIBUTES {
    my ($package, $subref) = @_;
    my $attrs = $attrs{ refaddr $subref };
    return @$attrs;
}

1;

Now, in MyClass and all its subclasses, you can use arbitrary attributes, and query them using attributes::get():

package SomeClass;
use base 'MyClass';
use attributes;

# set attributes
sub hello :Foo :Bar { }

# query attributes
print "hello() in SomeClass has attributes: ",
      join ', ', attributes::get(SomeClass->can('hello'));

1;
__END__
hello() in SomeClass has attributes: Foo, Bar

In summary, attributes don't do very much which on the other hand makes them very flexible: You can use them as real "attributes" (as shown in this example), implement something like decorators (see Sinan's answer), or for your own devious purposes.

trendels