Search code examples
perlsubroutine

When should I use subroutine attributes?


I don't grok Perl subroutine attributes at all.

I have never seen them in actual code and perldoc perlsub and the perldoc attributes fail to answer my questions:

  • What are attributes useful for?
  • What do they bring to the table that is not already present in Perl best practices?
  • Are there any CPAN modules (well-known or otherwise) that make use of attributes?

It would be great if someone could put together a detailed example of attributes being used the way they should be.


For those who are as clueless as me, attributes are the parameters after the colon in the attributes SYNOPSIS examples below:

sub foo : method ;
my ($x,@y,%z) : Bent = 1;
my $s = sub : method { ... };

use attributes ();  # optional, to get subroutine declarations
my @attrlist = attributes::get(\&foo);

use attributes 'get'; # import the attributes::get subroutine
my @attrlist = get \&foo;

Solution

  • Attributes allow you annotate variables to perform auto-magic behind the scenes. A similar concept is java annotations. Here is a small example that might help. It uses Attribute::Handlers to create the loud attributes.

    use Attribute::Handlers;
    
    sub UNIVERSAL::loud : ATTR(CODE) {
        my ( $pkg, $sym, $code ) = @_;
        no warnings 'redefine';
        *{$sym} = sub {
            return uc $code->(@_);
        };
    }
    
    sub foo : loud {
        return "this is $_[0]";
    }
    
    say foo("a spoon");
    say foo("a fork");
    

    Whenever a sub is declared with the loud attribute the UNIVERSAL::loud callback triggers exposing meta-information on the sub. I redefined the function to actually call an anonymous sub, which in turn calls the original sub and passes it to uc

    This outputs:

    THIS IS A SPOON
    THIS IS A FORK
    

    Now let's looks a the variable example from the SYNOPSIS:

    my ($x,@y,%z) : Bent = 1;
    

    Breaking this down into small perl statement without taking into account attributes we have

    my $x : Bent
    $x = 1;
    
    my @y : Bent
    @y = 1;
    
    my %Z : Bent
    %z = 1;
    

    We can now see that each variable has been attributed the Bent annotation in a concise way, while also assigning all variables the value 1. Here is a perhaps more interesting example:

    use Attribute::Handlers;
    use Tie::Toggle;
    
    sub UNIVERSAL::Toggle : ATTR(SCALAR) {
        my ($package, $symbol, $referent, $attr, $data, $phase) = @_;
        my @data = ref $data eq 'ARRAY' ? @$data : $data;
        tie $$referent, 'Tie::Toggle', @data;
    }
    
    my $x : Toggle;
    
    say "x is ", $x;
    say "x is ", $x;
    say "x is ", $x;
    

    Which outputs:

    x is 
    x is 1
    x is 
    

    You can use this to do logging, create test annotations, add type details to variables, syntactic sugar, do moose-ish role composition and many other cool things.

    Also see this question: How do Perl method attributes work?.