Sign In/My Account | View Cart  
advertisement


Listen Print

POOL
by Simon Cozens | Pages: 1, 2

Another Example

Now that I had this neat tool for generating modules, I set it to work on the next module I wrote; this was a variant of Tree::Trie, a class to represent the trie data structure. Tries are a handy way of representing prefix and suffix relationships between words. They're conceptually simple; each letter in a word is inserted into a tree as the child of the previous letter. If we wanted a trie to count the prefices in the phrase THERE IS A TAVERN IN THE TOWN, then we would first insert the chain T-H-E-R-E-#, then I-S-#, then A-#, and so on, where # represents "end-of-word". We'd end up with a trie looking like this:

An example of a trie
Figure 1: An Example of a Trie

Tree::Trie is good at this sort of thing, but it didn't do a few extra things I needed, so I wrote an internal-use module called Kasei::Trie; this was the POOL file I used to generate it:


    Kasei::Trie - Implement the trie data structure
        DESCRIPTION

    "Trie"s are compact tree-like representations of multiple words, where
    each successive letter is introduced as the child of the previous.

        EOD
        @children {}
        insert

    Kasei::Trie::Node
        @children
        ->@data

The main class, Kasei::Trie, has a constructor with one instance variable that is initialized to be an empty hash reference, and one method, insert. There's also a secondary class representing each node in the trie, which has its own children, and has a data variable with its own accessor.

After generating this with POOL, all I needed to do was to fill in the code for the insert method, and modify some tests. A manifest, Makefile.PL, test suite with nine tests, and 161 lines of code and documentation were automatically created for me. I suspect that POOL saved me one to two hours.

The High Dive

Let's now take a look at the templates that make this all happen. The main template is called module, and it looks like this:


    package [% module.package %];
    [% INCLUDE module_header %]
    =head1 SYNOPSIS

    [% INCLUDE synopsis %]
    =head1 DESCRIPTION

    [% module.description  %]

    =head1 METHODS
    [% 
        FOREACH method = module.methods;
        INCLUDE do_method;
        END
    %]

    [% INCLUDE module_footer %]

    1;

As you can probably guess, in the Template Toolkit language, interesting things happen between [% and %]. Everything else is just output wholesale, but all kinds of things can happen inside the brackets. The first thing that happens is that we look at the module's package name. All the data we've collated from the parsing phase is stuffed into a hash reference, which we have passed into the template as module. The dot operator in Template Toolkit is a general do-the-right-thing operator that can be a method call, a hash reference look-up or an array reference look-up. In this case, it's a hash reference look-up, and we perform the equivalent of $module->{package} to extract the name.

Template Toolkit's [% INCLUDE template %] directive looks for the file template in its template path, processes it passing in all the relevant variables, and includes its output. So after the initial package ...; line, we include another template that contains everything that goes at the top of the module. As we'll see later, part of the beauty of templating things this way is that you can override templates by placing your own idea of what should go at the top of a module into your private version of module_header earlier in the template path, in a sense "inheriting" from the base set of templates.

Similarly, we include a file that will output the synopsis, and output the description that we collected between the DESCRIPTION and EOD lines of our POOL definition file.

Next, we want to document the various methods and output the code for them. POOL will have placed all the metadata for the methods we've defined, plus a constructor, in the appropriate order in the methods hash entry of module. As this is an array reference, we want to use a foreach-style loop to look at each method in turn. Not surprisingly, Template Toolkit's foreach-style loop is called FOREACH.

So this code:


    [% 
        FOREACH method = module.methods;
        INCLUDE do_method;
        END
    %]

will set a variable called method to each method in the array, and then call the do_method template. This simply dispatches to appropriate templates for each type of method. For instance, there's the set of templates for the "delegate" style; delegate_code looks like this:


    sub [% method.name %] {
        my $self = shift;
        return $self->[% method.via %]->[% method.how %](@_);
    }

Whereas the documentation template contains some generic commentary:


    =head2 [% method.name %]

    [% INCLUDE delegate_synopsis -%]
    Delegates to the [%method.how%] method of this object's [%method.via%].

    =cut

The synopsis that appears in the documentation here and in the synopsis at the top of the file simply explains how the delegation is done:


    $self->[% method.name %](...); # $self->[% method.via %]->[%method.how%]

Of course, there are some templates that are a little more complex, particularly those that generate the tests, but the main thing is that you can override any or none of those. If you don't like the standard same-terms-as-Perl-itself licensing block that appears at the end of the module, then create a file called ~/.pool/license containing:


    =head1 LICENSE

    This module is licensed under the Crowley Public License: do what thou
    wilt shall be the whole of the license.

POOL will pick up this template and use it instead of the standard one.

There's No P in Our POOL

When I started planning this article in the bath this morning, I realized that POOL is actually fantastically badly named; there's nothing actually Perl-specific about the language itself, and it's a handy definition language for any object-oriented system. Hence, I hereby retroactively name the project "the POOL Object Oriented Language", which also satisfies the recursive acronym freaks. But can we, using the same parser and templating system, turn POOL files into other languages? Of course we can; this is all part of the flexibility of the Template Toolkit system. What's more, we don't even have to override all of the templates in order to do so, just some of them. For instance, here's a Ruby equivalent of accessor_code:


    [% IF method.ro == "ro"; %]
        attr_reader :[% method.name %]
    [% ELSE; %]
        attr_accessor :[% method.name %]
    [% END; %]

do_method and module_footer, however, never need to change, since all they do is include other methods. With a complete set of toolkits, the same POOL description can be used to output a Perl, Ruby, Python, Java and C++ implementation of a given class.

Going Deeper

When Frans Hals' famous painting "The Laughing Cavalier" was being examined in a museum's labs, someone had the bright idea of putting it through an X-ray machine. When they did this, they were amazed to find underneath the famous painting a completely different work -- a painting of a young girl. They then adjusted the settings on the X-ray machine and tried again, and underneath the young girl, they found another painting. Since then, it's been common practice to X-ray pictures, and art historians have found many layers of paint underneath some of the most-famous pictures.

What's this got to do with POOL? Well, very little, but I wanted to throw that in. Since I realised that POOL's templates can be inherited so easily, I've had the idea of POOL "flavors"; coherent sets of templates that can be layered like oil paintings to impart certain properties to the output.

For instance, at the moment, POOL outputs unit tests in separate files in the t/ directory, one for each class. Some people, however, prefer to have their tests in the module right alongside the documentation and implementation, using the method described in Test::Inline. Well, there's no reason why POOL shouldn't be able to support this. All you'd need to do is create a new directory, let's say testinline/, and put a modified version of do_method in there which says something like:


    [% INCLUDE method_pod %]
    =head2 begin testing
    [% INCLUDE method_test %]
    =cut
    [% INCLUDE method_code %]

Next, arrange for testinline/ to appear in the Template Toolkit template path, and magically your tests will appear in the right place.

It's not inconcievable that multiple "flavours" could combine in order to theme a module; for instance, you might want a module which uses Test::Class for its tests, and Module::Build for its build file, with a BSD license flavor and Class::Accessor for its accessors instead of having them explicitly coded. Conceptually, you'd then say:


    pool --flavours=testclass,modulebuild,bsdlicense,classaccessor mymodule.pool

and the module would come out just as you want. This hasn't happened yet for two reasons: First, although it's only a two- or three-line change to the pool parser to support pushing these directories onto the template path, I haven't needed it yet so I haven't done it, and second, because I haven't written any flavors yet. But it's easy enough to do.

Other future directions for POOL include a syntax for class methods and class variables, support for other languages as mentioned about, (which basically means ripping out the hard-coding of MANIFEST, Makefile.PL and so on and replacing that with a more flexible method) and other minor modifications. For instance, I'd like some syntax to specify dependencies; other Perl modules which will then be use'd in the main modules and which would be named at the appropriate place in the Makefile.PL. And, of course, there's building up a library of flavors, including "total conversion" flavors like Ruby and Python.

The one thing that's becoming really, really important is the need for nondestructive editing -- the ability to fill in some additional code for a method, then regenerate the class from a slight change to the POOL file without losing the new method's code. I'm going to need to add that soon to allow for iterative redesigning of modules.

But the main thing about POOL is what it does now -- it saves me time, and it takes away the drudgery of writing OO classes in Perl.

And I will finish Devel::DProfPP soon. I promise.