Beefy Boxes and Bandwidth Generously Provided by pair Networks
Think about Loose Coupling
 
PerlMonks  

Function/Method Annotations

by TedYoung (Deacon)
on May 26, 2006 at 02:39 UTC ( [id://551748]=perlmeditation: print w/replies, xml ) Need Help??

Java and C# have a concept called an annotation. Annotations allow you to set properties of methods. After all, in a truly object oriented language, methods and functions should be objects too. In JavaScript, for instance, functions are objects and, as with any JavaScript object, you can set arbitrary properties on it, even give it methods. Let's see how we can apply this to Perl.

So, why do methods need properties? Sometimes these annotations can be used to effect compilation or execution of a method. For instance, in Java, marking a method with the @deprecated annotation tells the compiler this methods should not be used in new code. When you compile something that uses a method marked as deprecated, the compiler will issue a warning.

More often than not, however, annotations are used as tags to be used by toolsets that may process code or an object instance. For example, if you are rolling your own object persistence engine, you could specify the underlying persistence properties for the database (i.e. field size, precision, nullability). When your engine goes to create a table, it can inspect the annotations of the accessor methods and create a table accordingly.

Perl has something close. It has attributes. In fact, some predefined attributes are no different than annotations (i.e. method, lock, lvalue). Aside from those, however, attributes only brings you half of the way. You can use Attribute::Handlers to define your attributes, but you have to collect the data and store it some where.

To digress for a minute, a common way of applying an attribute to a function might be to modify the function directly:

use Attribute::Handlers; use Carp; sub private :ATTR(CODE) { my ($package, $symbol, $referent, $attr, $data, $phase) = +@_; my $name = *{$symbol}{NAME}; *{$symbol} = sub { croak "Function $name is declared private in $ +package" if caller ne $package; $referent->(@_) } }

At this point, let me interject that most of the code here is for demonstration. So, it doesn't use standard concepts like strict and warnings, it is not robust, hasn't been well tested, and may not even compile. :-)

So, this code creates a :private attribute. Any method marked as :private will have a check appended to it to ensure it is only called from within the defining package.

Back to annotations: For the purpose of this document, I am defining the difference between and attribute and an annotation such that an annotation is an attribute that stores the attributed data somewhere that can be accessed later.

Let's say we want to write a basic event handler for a website. So, when a person clicks on a link or submits a form, along with the data, and field called event would be included. The value of this parameter would be the name of the function we want executed in response of the user agent request (i.e. it hits the database and generates HTML).

Another note here: this is a demonstration. If this sounds like a good idea, please consider using one of the many prebuilt servlet/web app frameworks already out there.

Well, we want to explicitly mark which functions can serve as events. After all we don't want the value of event to be forged to "rmtree" if we had use File::Path qw/rmtree/ at the top of our script, right? One way of indicating which functions are to serve as events would be through the use of annotations.

Again, Perl gives us attributes, but we need to save the list of event handlers somewhere to for reference to by our dispatcher:

package EventHandlers; our %EventHandlers; use Attribute::Handlers; use Carp; sub Event :ATTR(CODE) { my ($package, $symbol, $referent, $attr, $data, $phase) = +@_; my $name = *{$symbol}{NAME}; $EventHandlers{$name} = 1; }

Now, any function marked as :Event will have its name stored in the hash %EventHandlers. This hash can then be used by the event dispatcher to ensure the value of event is a valid event.

Unfortunately, we had to do this bookkeeping manually. In an object oriented environment, one might like to have the definition of annotations somewhat automated, and the retrieval of annotations associated with the individual functions themselves.

Here is an example of how we might do that. First, we need an annotations package;

package Annotations; my %Annotations; sub Annotate { my ($sub, %annotations) = @_; bless $sub, Annotations; my $h = $Annotations{$sub} ||= {}; @$h{keys %annotations} = values %annotations; } sub annotation { my ($this, $name) = @_; $Annotations{$this}{$name} }

It will make better sense when we see it used:

package Events; use Annotations; use Attribute::Handlers; sub Event :ATTR(CODE) { my ($package, $symbol, $referent, $attr, $data, $phase) = +@_; Annotations::Annotate($referent, eventhandler => 1); }

The Events package provides an attribute called :Event. Every method marked with that attribute is automatically annotated as an eventhandler. This dispatcher might look like this:

if (my $event = MyEvents->can($CGI->param('event'))) { die unless $event->annotation('eventhandler'); MyEvents->$event(); }

You can annotate functions through attributes like this, or by calling the Annotate method directly. But by doing this, we are turning methods into objects that track arbitrary properties.

This can be easily extended to provide functions to return all functions in a package that have a certain annotation. Also, one can support the inheritance of annotations through @ISA. And you can combine attribute data (:Attribute(data data data)) to support more flexible annotations. Etc.

I would have rather had the syntax:

$event->{eventhandler}

But my version of perl (Active State 5.8.7) didn't like applying an overload to this. Oh well. In fact, one might think that blessing functions and calling methods on them strange. For all I know, this may be buggy in some edge cases. You can opt for a functional interface where you would query an annotation like this:

annotation($event, 'eventhandler')

Anyway, I was lonely and wanted to talk a little.

Update: For those of you who are inpsired to play around with attributes, here are some modules on CPAN that make use of them:

I don't particularly endorse any of these, they just looked interesting while doing a quick search on CPAN.

Update: Fixed some speeling erors. Thanks shenme.

Update: Added readmore tags.

Ted Young

($$<<$$=>$$<=>$$<=$$>>$$) always returns 1. :-)

Replies are listed 'Best First'.
Re: Function/Method Annotations
by sarani (Sexton) on May 26, 2006 at 08:39 UTC
    Woot, what a way to "just talk a little". I "learnt a little" today, so thank you.
Re: Function/Method Annotations
by ForgotPasswordAgain (Priest) on May 26, 2006 at 16:12 UTC
    Annotations allow you to set properties of methods. After all, in a truly object oriented language, methods and functions should be objects too.

    Are the annotations also objects?

      Of course they can be. :-) In Java, for instance, they are anonymous implementations of interfaces. The Perl techniques I use above could easily be extended to make the annotations objects.

      Ted Young

      ($$<<$$=>$$<=>$$<=$$>>$$) always returns 1. :-)

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://551748]
Approved by shenme
Front-paged by neniro
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others scrutinizing the Monastery: (5)
As of 2024-04-19 23:04 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found