http://qs1969.pair.com?node_id=556016

Item Description: Sane source filtering for the rest of us

Review Synopsis:

How many of you have written code that looks something like:

package Some::Class; use strict; use warnings; sub new { my $class = shift; return bless { @_ }, $class; } sub foo { my $self = shift; $self->{foo} = shift if @_; return $self->{foo}; } sub bar { my $self = shift; $self->{bar} = shift if @_; return $self->{bar}; } sub baz { my $self = shift; $self->{baz} = shift if @_; return $self->{baz}; } sub do_something_useful { ... }
Come on, raise your hands. I know I've done this at least a hundred times. Then, I learned about closures and went back and rewrote that code to look something like:
package Some::Class; use strict; use warnings; sub new { my $class = shift; return bless { @_ }, $class; } foreach my $name ( qw( foo bar baz ) ) { no strict 'refs'; *{ __PACKAGE__ . "::$name" } = sub { my $self = shift; $self->{$name} = shift if @_; return $self->{$name}; }; } sub do_something_useful { ... }
Now, instead of 98% of the Perl community being able to maintain my code, I'm down to 0.98%. Several managers I've worked for had made me take out code like that, and for good reason. Just because they hired a Perl expert to write the code doesn't mean that they'll be able to hire someone like that to maintain the code. So, it's back to repetition, right?

<Trumpets sound in the distance /> Module::Compile::TT to the rescue! That code using typeglobs and closures now looks like:

package Some::Class; use strict; use warnings; sub new { my $class = shift; return bless { @_ }, $class; } use tt; [% FOREACH name IN [ 'foo', 'bar', 'baz' ] %] sub [% name %] { my $self = shift; $self->{[% name %]} = shift if @_; return $self->{[% name %]}; } [% END %] no tt; sub do_something_useful { ... }
Whoa! That actually looks readable! Everyone knows how to read TT directives (or they're close enough to your favorite templating module as to be no difference).

But, isn't this a source filter? Well, technically, it is. But, there's a major difference between this and Filter::Simple. Module::Compile::TT compiles this once and installs a .pmc file that you can look at and edit. Or, you could just run TT against this module and see what would happen.

Contrast that to Filter::Simple that won't generates potentially anything and you have no (sane) way of finding out what happened.

The real dealbreaker for me is that I feel pretty sure I could take this to any manager I use to work for and they would all be comfortable with that kind of code in their production codebases. This is code that can be maintained by the masses.

Replies are listed 'Best First'.
Re: Module::Compile::TT
by nothingmuch (Priest) on Jun 17, 2006 at 20:41 UTC
    Note that Module::Compile has many goodies:
    • It filters into a .pmc file, so the filter doesn't have to be run every load
    • It will regenerate the code when needed (when the original has changed)
    • It allows you to ship the "compiled" .pmc instead or in addition to the .pm, so that your code doesn't need to depend on the actual filter tool to work
    ingy++! audreyt++!

    -nuffin
    zz zZ Z Z #!perl
Re: Module::Compile::TT
by Arunbear (Prior) on Jun 17, 2006 at 23:21 UTC
    When I read through your example, Class::Accessor immediately came to mind, so I'm not sure what benefit a source filtering approach brings to this problem. There doesn't seem to be much documentation for Module::Compile::TT, so how else can be used e.g. can you use it to simulate macros?
      Accessors are just a clear example.

      Nowadays I use Moose for them, or if I can't then Class::Accessor.

      I needed tt for a set of methods that operate on two different classes in almost the same way. It's still just boilerplate code (the object that needed this code gen was a factory object with some convenience routines).

      As for macros - C preprocessor style ones, that is - you can use TT macros. See Template::Manual::Directives.

      -nuffin
      zz zZ Z Z #!perl
Re: Module::Compile::TT
by tinita (Parson) on Jun 18, 2006 at 13:07 UTC
    [% FOREACH name IN [ 'foo', 'bar', 'baz' ] %] sub [% name %] { my $self = shift; $self->{[% name %]} = shift if @_; return $self->{[% name %]}; } [% END %]
    well, to be honest, I don't find that more readable than creating the subs with writing them directly into the symbol table. I find it less readable.

    I see no comments in your original code. With some short comment an average perl programmer should be able to understand what the code is doing and how to maintain it.
    If somebody doesn't understand it, I guess they wouldn't understand the TT-example, either (they'd have to understand how a source filter works). I think it's better to teach them what an anonymous sub is than how to use inline-TT-code.
    And, additionally, not everybody knows or likes TT syntax.

Re: Module::Compile::TT
by merlyn (Sage) on Jun 17, 2006 at 21:06 UTC
      Module::Compile was written to solve a slightly different problem than Inline. The api is simpler, and better suited for translation based operations (like source filters) than interfacing foreign runtimes with additional precompilation, mostly via introduction of symbols as alias for foreign functions (by overriding load or relying on DynaLoader). For these reasons Module::Compile is more appropriate. Also, Inline's caching is not based on Perl's .pmc infrastructure, so shipping cached code is trickier.

      Note that the one of the authors of Module::Compile is also the author of Inline.

      Short answer: Inline::TT would be ~ 5x the amount of work that Module::Compile::TT was.

      -nuffin
      zz zZ Z Z #!perl
Re: Module::Compile::TT
by schwern (Scribe) on Sep 17, 2007 at 04:05 UTC
    Maybe I'm being old fashioned, but didn't we invent subroutines to hide complexity?