Tuesday, May 25, 2010

Glimpses of Glorious Moose Traits

Anything that makes a software developer's life easier is good. Moose 'traits' fits right in that category.

A diversion .. When I first came upon Perl, what impressed me most was the ease with which data structures could be constructed, manipulated and extended. The ease of stuffing any kind of data - whether an object, array or even another hash - into a hash was a simplification that made coding a breeze! It is features like this in programming languages that free-up a developer's mental infrastructure from book-keeping to creative implementation - so s/he can focus on shaping an application rather than mental retention of myriad mettlesome minutiae.

But therein lies the bug-feature rub, if you catch my drift. This feature extracts a price. You've heard it, don't make me say it - Perl is slow. Perl is a memory-hog. And the inevitable comparisons to to C++ and Java. Needless to say, having more than one tool in one's belt is essential - nay, indispensable!

I can take comfort in that. You see, I love Taekwon-do, am devoted to this Korean martial art, and my sa-bum tells me "To excel in Taekwon-do, you must know Judo." Or Aikido. Or La Savate. Or Muy Thai. Or Rifle Shooting. The point is, unless you immerse yourself into the 'other' side, you risk getting locked into patterns of thinking that ultimately crimp your style.

So hear ye, developers, and be comforted. When slapped with criticism, turn the other cheek. Don't bash Java. Resist the temptation to ridicule .Net. Rather, learn about them! Then, when faced with an interesting problem, make the best choice. Yes, life is tough and there is no other way but to keep at it.

I must say though, as one having ploughed my way through C, C++ and .Net, I know of no other language that gives me as much creative freedom as my beloved Perl. Coding in Perl, particularly with the MOOSE object-oriented framework, is on par with other pleasures in life like watching sunrise from a mountain top or making love to a beautiful woman or making love to a beautiful woman at sunrise on a mountain top.

And with that sanguine thought, onward to traits.

Consider the Remote Control class from the previous example. (See: Command Pattern.) The Remote, which is an 'Invoker', is composed of 'Commands', each of which wraps around a 'Receiver', wherein resides behavior and the methods to access it. Dense though this clap-trap is, cutting to the chase a class is composed of other classes in sets. Savvy?

Consider the typical implementation.

package RemoteControl;
use Moose;

use Command;


has 'onCommands' => (
is => 'rw',
isa => 'ArrayRef[Command]',
default => sub { [] },
);

has 'offCommands' => (
is => 'rw',
isa => 'ArrayRef[Command]',
default => sub { [] },
);

Consider access to the slots in the BUILD method.

sub BUILD {
my $self = shift;
for (my $i = 0; $i < 7; $i++) {
push @{$self->onCommands}, NoCommand->new;
push @{$self->offCommands}, NoCommand->new;
}
}

A bit clumsy, don't you think? The sub (which is a method, by the way) unpacks the class to get to the underlying slot. Not a good thing. And this has to happen each time the slot is accessed as a container - to push/pop, count, store at/retrieve from an indexed location, sort, filter via map/grep, etc. Is there a way one could get a handle on this rum business?

There is. Consider the implementation using traits. See the rum.. err, handle?

package RemoteControl;
use Moose;
use Command;


has 'onCommands' => (
traits => ['Array'],
is => 'rw',
isa => 'ArrayRef[Command]',
default => sub { [] },
handles => {
all_onCommands => 'elements',
add_onCommand => 'push',
map_onCommands => 'map',
filter_onCommands => 'grep',
find_onCommand => 'first',
get_onCommand => 'get',
join_onCommands => 'join',
count_onCommands => 'count',
has_onCommands => 'count',
has_no_onCommands => 'is_empty',
sorted_onCommands => 'sort',
},
);

has 'offCommands' => (
traits => ['Array'],
is => 'rw',
isa => 'ArrayRef[Command]',
default => sub { [] },
handles => {
all_offCommands => 'elements',
add_offCommand => 'push',
map_offCommands => 'map',
filter_offCommands => 'grep',
find_offCommand => 'first',
get_offCommand => 'get',
join_offCommands => 'join',
count_offCommands => 'count',
has_offCommands => 'count',
has_no_offCommands => 'is_empty',
sorted_offCommands => 'sort',
},
);

This is what access to containers now looks like, with traits involved:

sub BUILD {
my $self = shift;
for (my $i = 0; $i < 7; $i++) {
$self->add_onCommand(NoCommand->new);
$self->add_offCommand(NoCommand->new);
}
}

Amen!

No comments:

Post a Comment