POOL
The Perl Object Oriented Language
by Simon CozensApril 22, 2003
Splashing Around
One of the reasons that I always feel I never get anything substantial done in Perl is that I'm always distracted unduly by subtasks, particularly metaprogramming. I write so many "labor-saving" modules that I never get around to doing the original labor in the first place.
For instance, I wanted to write something to handle my accounts; I
needed something to handle command-line options but I couldn't be
bothered with the Getopt::Long rigmarole, so I wrote Getopt::Auto.
Next, I needed to parse a simple configuration file and I couldn't face
writing yet another colon-separated file parser, so I wrote
Config::Auto. Finally, I wrote something that examined a database
schema and wrote Class::DBI packages for each table -- all
helpful tasks, and they meant I would never have to worry about
configuration files or command-line options again. But, of
course, I forgot about my accounts-handling application.
Something like this happened again recently. I started writing a module
I'm calling Devel::DProfPP, which parses the data output by
Devel::DProf in a neat object-oriented way. I was about five minutes
into it when found myself writing:
=head1 CONSTRUCTOR
$object = Devel::DProfPP->new( %options )
Creates a new C<Devel::DProfPP> instance.
=cut
sub new {
my ($class, %opts) = @_;
bless { %opts }, $class;
}
And then I wrote my test: (I'm a bad boy, I write my tests after I write the code)
use Test::More;
use_ok("Devel::DProfPP");
my $x = Devel::DProfPP->new();
isa_ok($x, "Devel::DProfPP");
...
Nothing strange about that, you might think. It's something I've written
a dozen of times before, and something I will probably write a
dozen times again. And that's when it hit me. I don't
want to spend my time pounding out constructors and accessors,
documentation that's practically identical from class to class, tests
that are eminently predictable, and I never, ever wanted to have to
write my $self = shift; again.
There are two ways to solve this. Now I'm not going to last long as
editor of perl.com if I suggest everyone should do the first, and
switching to Ruby wouldn't help with the documentation and tests part of
the problem anyway.
The second is, of course, to make the computer do the hard work. I
sketched out a short description of what I wanted in my
Devel::DProfPP module, and wrote a little parser to read in that
description, and then had some code generate the module. Then, I made a
critical decision. I had just been doing some work with Template
Toolkit, and wanted some more opportunity to play with it. So instead of
hard-coding what the output module ought to look like, I simply passed
the parsed data structure to a bunch of templates and let them do the
work. This gave me an amazing amount of flexibility in terms of styling
the output, and that's where I think the power of this system, the Perl
Object Oriented Language, (POOL) lies.
In order to investigate that, let's look at some POOL files and the output they generate, and then we'll examine how the templates work and fit together.
Diving In
The POOL language is very, very ad hoc. It's bizarre and inconsistent, but that's because it was created by rationalizing a module description I scribbled down late one night. But it does the job. It's essentially a brain dump, and if your brain happens not to work like mine, then you might not like it; if yours does work like mine, my commiserations.
The POOL distribution, available from CPAN, ships with a handy reference
manual, and that very description; these are the original notes I made
when I was mocking up Devel::DProfPP, and they look like this:
Devel::DProfPP - Parse C<Devel::DProf> output
DESCRIPTION
This module takes the output file from L<Devel::DProf> (typically
F<tmon.out>) and parses it into a Perl data structure. Additionally, it
can be used in an event-driven style to produce reports more
immediately.
EOD
@fh
->@enter || sub {}
->@leave || sub {}
ro->@stack []
@syms []
parse
top_frame ->stack->[0]
The first line should be familiar to anyone who writes or uses Perl modules: it's the description line that goes at the top of the documentation. It's enough to identify the class name and provide some documentation about it.
The next thing is the module description, again for the documentation, which
begins with DESCRIPTION and ends with EOD.
When you look at the things starting with @, don't think Perl, think Ruby -
they're not arrays, they're instance variables. Our Devel::DProfPP object
will contain a filehandle to read the profiling data from, a subroutine
reference for what to do when we enter a subroutine and when we leave one,
the current Perl stack, and an array of symbols to hold the subroutine names.
These instance variables come in three types. The first are variables
that the user doesn't set, but come with every new instance. I call
these "set" variables, because they're come set to a particular value.
Then there are "defaulting" instance variables, which the user may
specify but otherwise default to a particular value. And then there are
just ordinary ones, which are not initialized at all. Thankfully for me,
the Devel::DProfPP brain dump contained all three types.
The symbol table and the stack are "set" variables. They come set to the an empty array reference; we signify this by simply putting an empty array reference on the same line.
@syms []
The enter subroutine, on the other hand, is a defaulting variable. (Don't worry about the arrow for now.) If the user doesn't specify an action to be performed when the profiler says that a subroutine has been entered, then we want to default to a coderef that does nothing.
In Perl, when we want to default to a value, we say something like:
$object->{enter} = $args{enter} || sub {};
So the POOL encoding of that is:
@enter || sub {}
Finally, there's the filehandle, which the user supplies. Nothing special has to occur for this instance variable, so we just name it:
@fh
From this, we know enough to create a constructor like so:
sub new {
my $class = shift;
my %args = @_;
my $self = bless {
fh => $args{fh},
enter => $args{enter} || sub {},
leave => $args{leave} || sub {},
syms => [],
stack => [],
}, $class;
return $self;
}
And that's precisely what POOL does. After the constructor, comes the
accessors; we want to be able to say $obj->enter to retrieve the
on-enter action, for instance. This thought led naturally to the syntax
->@enter || sub {}
When POOL sees an arrow attached to an instance variable, it creates an accessor for it:
sub enter {
my $self = shift;
if (defined @_) { $self->{enter} = @_ };
return $self->{enter};
}
The stack accessor is an interesting one. First, we only want this to be
an accessor and not a mutator -- we really don't want people modifying the
profiler's idea of the stack behind its back. This is signified by the
letters ro (read-only) before the accessor arrow.
ro->@stack []
A further twist comes from the fact that POOL is still trying to DWIM
around my brain and my brain expects POOL to be very clever indeed.
Because we have declared stack to be set to an array reference, we
know that the stack accessor deals with arrays. Hence, when
$obj->stack is called, it should know to dereference the
reference and return a list. This means the code ends up looking like
this:
sub stack {
my $self = shift;
return @{$self->{stack}};
}
Aside from constructors and accessors, POOL knows about two other styles of method. (For now; there are more coming.) There are ordinary methods, which are simply named:
parse
Sadly we can't DWIM the entire code for this method, so we generate the best we can:
sub parse {
my $self = shift;
#...
}
And the final style is the delegate. The sad thing about the delegate
given in the DProfPP example is that it doesn't actually work, but we'll
pretend that it does. Delegates are useful when you have an object that
contains another object; you can provide a method in your class that
simply diverts off to the contained object. For instance, if we have a
class representing a mail message, then we may wish to store the head and body
as separate objects inside our main object. (Mail::Internet does something
like this, containing a Mail::Header object.) Now we can provide a
method called get_header, which simply locates the header object and passes
on its arguments to that:
sub get_header {
my $self = shift;
$self->header->get(@_);
}
In POOL lingo, this is a delegate via the header method, and get
tells us how to do the delegation. It would be specified like this:
get_header ->header->get
Notice that this is precisely what appears in the middle of the Perl code
for this method. An additional feature is that the "how" part of the delegation
is optional. If we were happy for our top-level method to be called get
instead of get_header, then we could say:
get ->header->
To me, this symbolizes going "through" the header method in order to call
the get method.
These are the basics of the POOL language, and we've seen a little of the
code it generates. It also generates a full set of documentation and
tests, as well as a MANIFEST file and Makefile.PL or Build.PL
file, but we'll look at those a little later.
In case you're interested, the reason why the delegation in the example doesn't work is because I was being too clever. I thought I could say:
top_frame ->stack->[0]
and have a top_frame method which "calls" [0] on the stack array
reference, returning its first entry. This doesn't work for two reasons.
First, I was too clever about ->stack and now it returns a list
instead of an array reference. Second, delegates need to pass arguments,
so POOL ends up generating code that looks like:
return $self->stack->[0](@_);
(The third reason, of course, is that the top of the stack when
represented as an array is element -1, not element 0. Oops.)
I thought about fixing this to do what I really, really mean, but decided that would be too nasty.
Pages: 1, 2 |

