Attributes are a method of attaching out of band information onto variables or functions in modern perls. This is very useful for storing metadata - data about the data or code you are creating.
They can be used for a variety of tasks, and if used properly can be used to add extra functionality or constraints to your code without overly complicating the code layout.
Attribute::Handlers allows you to write your own attributes in Perl that your scripts can use.
Using (as opposed to writing) attributes is simple. For example, the Attribute::TieClasses module allows us an alternative interface to tie.
#!/usr/bin/perl
# turn on the safety features use strict; use warnings;
# load the module that defines our attributes use Attribute::TieClasses;
# create a variable and stick the magical 'Toggle' attribute on it my $foo : Toggle;
# print out $foo four times. for (1..4) { if ($foo) { print "it's true!\n" } else { print "it's false!\n" } }
The Toggle
note tells Perl that the variable should be tied with
the Tie::Toggle
class, meaning every time it's read it alternates
between being true and false. Hence, when we run this code we get:
it's false! it's true! it's false! it's true!
Attributes can also be attached to subroutines. For example, the Attribute::Attempts module allows you to put a little note on a subroutine that says that it an exception is thrown within that routine Perl should simply wait a bit and run the subroutine again.
# alter db will try three times before failing sub alter_db : attempts(tries => 3, delay => 2) { # connect to the database or throw an exception my $dbh = DBI->connect("DBD::Mysql:foo", "mark", "opensaysme") or die "Can't connect to database";
# tell the dbi to throw an exception if there's a problem local $dbh->{RaiseException} = 1;
# try doing some sql $dbh->do("alter table items change pie pies int(10)"); }
Note how the attempts
attribute itself takes arguments (in
this case the number attempts Perl should make to run the subroutine
before giving up, and the seconds it should wait between attempts.)
As with all attributes these must be all on the same line as the
sub
declaration itself.
Attribute::Handlers works by defining a new attribute ATTR
that
itself can be used to create new attributes by applying it to
approperately named subroutines.
For example, we can write a module that defines a Monitored
attribute
that when applied to a variable indicates that the contents of the
variable should be monitored and printed out whenever the report
method is called.
We do this by defining a class with a Monitored
subroutine in it
and marking this subroutine with the ATTR
attribute.
package Attribute::Monitored;
# turn on perl's safety features use strict; use warnings;
# setup the ATTR attribute use Attribute::Handlers;
use Data::Dumper;
# our list of variables that we're monitoring our @monitoring;
# declare an attribute 'Monitored' that can be attached to scalars sub Monitored : ATTR(SCALAR) { my ($package, $symbol, $referent, $attr, $data, $phase) = @_;
# referent contains a reference to the variable that the attribute # was written next to. We simply record it. push @monitoring, $referent; }
sub report { # go through each of the variables that we're storing. foreach my $var (@monitoring) { # print out the variable print Dumper ${ $var }; } }
# return true to keep Perl happy 1;
This attribute is now accessible to any class that inherits from our class. package Fred; use base qw(Attribute::Monitored);
# declare a variable and make it monitored my $foo : Monitored = "bar";
# a subroutine to alter $foo sub foo { $foo = shift }
1;
We can now get reports like so:
use Fred; Fred->report; Fred->foo("baz"); Fred->report;
Which prints out:
$VAR1 = 'bar'; $VAR1 = 'Fred';
Though I've written quite a few attribute handlers, the code tends to be hard to follow and quite verbose - neither of which make ideal content for the advent calendar, even if the module deserves a mention.
The complexity comes from the wealth of information you're passed in each of your attribute subroutines each time the attribute is applied.
The name of the package the item the attribute is being applied to is located in.
The typeglob that contains the item, if there is one. For example, the typeglob can be used to replace a subroutine that an attribute is applied to with a wrapping subroutine (this is in fact how Attribute::Attempts functions. This value might be a constant "LEXICAL" or "ANON" instead of a typeglob if the item isn't being entered in the stash.
A reference to the variable that the attribute is being applied.
The name of the attribute that's being applied.
The arguments that were passed in the data. For example, in the code above we passed 'tries => 3, delay => 2' to the attempts subroutine and that would be found in this variable. Attribute::Handlers will attempt to turn these values into a Perl data structure, but failing this this will be just passed in as a string.
Where exactly the attribute is being executed in the lifetime of
the program (at BEGIN
time, CHECK
time, etc)
With all these options you should hopefully get an idea of what exactly is possible. With a reference to a variable you can tie the underlying variable to do 'interesting things' With a reference to a symbol table you can modify subroutines by wrapping them in external subroutines.