How do Perl method attributes work?
Asked Answered
C

2

49

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?

Changeup answered 12/6, 2009 at 14:51 Comment(0)
B
40

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 Mike Friedman's article), or for your own devious purposes.

Brigitte answered 12/6, 2009 at 15:27 Comment(0)
T
5

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.


I take from the article cited by the other answer:

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.

Trapani answered 12/6, 2009 at 15:1 Comment(2)
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.Brigitte
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.Trapani

© 2022 - 2024 — McMap. All rights reserved.