Tag: work

  • Taming the Moose: Classing up Perl attributes

    Taming the Moose: Classing up Perl attributes

    At my work, we exten­sive­ly use the Moose object sys­tem to take care of what would ordi­nar­i­ly be very tedious boil­er­plate object-​oriented Perl code. In one part of the code­base, we have a fam­i­ly of class­es that, among oth­er things, map Perl meth­ods to the names of var­i­ous calls in a third-​party API with­in our larg­er orga­ni­za­tion. Those pri­vate Perl meth­ods are in turn called from pub­lic meth­ods pro­vid­ed by roles con­sumed by these class­es so that oth­er areas aren’t con­cerned with said API’s details.

    Without going into too many specifics, I had a bunch of class­es all with sec­tions that looked like this:

    sub _create_method    { return 'api_add'     }
    sub _retrieve_method  { return 'api_info'    }
    sub _search_method    { return 'api_list'    }
    sub _update_method    { return 'api_update'  }
    sub _cancel_method    { return 'api_remove'  }
    sub _suspend_method   { return 'api_disable' }
    sub _unsuspend_method { return 'api_restore' }
    
    ... # etc.

    The val­ues returned by these very sim­ple meth­ods might dif­fer from class to class depend­ing on the API call need­ed, and dif­fer­ent class­es might have a dif­fer­ent mix of these meth­ods depend­ing on what roles they consume.

    These meth­ods had built up over time as devel­op­ers had expand­ed the class­es’ func­tion­al­i­ty, and this week it was my turn. I decid­ed to apply the DRY (don’t repeat your­self) prin­ci­ple and cre­ate them from a sim­ple hash table like so:

    my %METHOD_MAP = (
      _create_method    => 'api_add',
      _retrieve_method  => 'api_info',
      _search_method    => 'api_list',
      _update_method    => 'api_update',
      _cancel_method    => 'api_remove',
      _suspend_method   => 'api_disable',
      _unsuspend_method => 'api_restore',
    );

    At first, I thought to myself, These look like pri­vate read-​only attrib­ut­es!” So I wrote:

    use Moose;
    
    ...
    
    has $_ => (
      is       => 'ro',
      init_arg => undef,
      default  => $METHOD_MAP{$_},
    ) for keys %METHOD_MAP;

    Of course, I’d have to move the class­es’ with state­ments after these def­i­n­i­tions so the roles they con­sume could see” these runtime-​defined attrib­ut­es. But some of the meth­ods used to read these are class meth­ods (e.g., called as ClassName->foo() rather than $object->foo()), and Moose attrib­ut­es are only set after the con­struc­tion of a class instance.

    Then I thought, Hey, Moose has a MOP (meta-​object pro­to­col)! I’ll use that to gen­er­ate these meth­ods at runtime!”

    my $meta = __PACKAGE__->meta;
    
    while (my ($method, $api_call) = each %METHOD_MAP) {
        $meta->add_method( $method => sub {$api_call} );
    }

    The add_method doc­u­men­ta­tion strong­ly encourage[s]” you to pass a metamethod object rather than a code ref­er­ence, though, so that would look like:

    use Moose::Meta::Method;
    
    my $meta = __PACKAGE__->meta;
    
    while (my ($method, $api_call) = each %METHOD_MAP) {
        $meta->add_method( $method = Moose::Meta::Method->wrap(
          sub {$api_call}, __PACKAGE__, $meta,
        );
    }

    This was get­ting ugly. There had to be a bet­ter way, and for­tu­nate­ly there was in the form of Dave Rolskys MooseX::ClassAttribute mod­ule. It sim­pli­fies the above to:

    use MooseX::ClassAttribute;
    
    class_has $_ => (
      is      => 'ro',
      default => $METHOD_MAP{$_},
    ) for keys %METHOD_MAP;

    Note there’s no need for init_arg => undef to pre­vent set­ting the attribute in the con­struc­tor. Although they’re still Moose attrib­ut­es, they act like class meth­ods so long as the class con­sumes the roles that require them after the attribute definitions.

    Lastly, if we were using Moo as a light­weight alter­na­tive to Moose, I could have instead select­ed Toby Inksters MooX::ClassAttribute. Although it has some caveats, it’s pret­ty much the only alter­na­tive to our ini­tial class method def­i­n­i­tions as Moo lacks a meta-​object pro­to­col.

    The les­son as always is to check CPAN (or the appro­pri­ate mix of your language’s soft­ware repos­i­to­ry, forums like Stack Overflow, etc.) for any­thing that could con­ceiv­ably have appli­ca­tion out­side of your par­tic­u­lar cir­cum­stances. Twenty-​five years into my career and I’m still leap­ing into code with­out first con­sid­er­ing that some­one smarter than me has already done the work.

  • Some thoughts on Perl template processing

    Some thoughts on Perl template processing

    Template proces­sors and engines are one of those pieces of soft­ware where it seems every devel­op­er wants to rein­vent the wheel. Goodness knows I’ve done it ear­li­er in my career. Tell me if this sounds familiar:

    1. You need to mix data into a doc­u­ment so you start with Perl’s string inter­po­la­tion in "dou­ble quotes" or sprintf for­mats. (Or maybe you inves­ti­gate formats, but the less said about them the bet­ter.)
    2. You real­ize your doc­u­ments need to dis­play things based on cer­tain con­di­tions, or you want to loop over a list or some oth­er structure.
    3. You add these fea­tures via key­word pars­ing and escape char­ac­ters, think­ing it’s OK since this is just a small bespoke project.
    4. Before you know it you’ve invent­ed anoth­er domain-​specific lan­guage (DSL) and have to sup­port it on top of the appli­ca­tion you were try­ing to deliv­er in the first place.

    Stop. Just stop. Decades of oth­ers who have walked this same path have already done this for you. Especially if you’re using a web frame­work like Dancer, Mojolicious, or Catalyst, where the tem­plate proces­sor is either built-​in or plug­gable from CPAN. Even if you’re not devel­op­ing a web appli­ca­tion there are sev­er­al general-​purpose options of var­i­ous capa­bil­i­ties like Template Toolkit and Template::Mustache. Investigate the alter­na­tives and deter­mine if they have the fea­tures, per­for­mance, and sup­port you need. If you’re sure none of them tru­ly meet your unique require­ments, then maybe, maybe con­sid­er rolling your own.

    Whatever you decide, real­ize that as your appli­ca­tion or web­site grows your invest­ment in that selec­tion will only deep­en. Porting to a new tem­plate proces­sor can be as chal­leng­ing as port­ing any source code to a new pro­gram­ming language.

    Unfortunately, there are about as many opin­ions on how to choose a tem­plate proces­sor as there are tem­plate proces­sors. For exam­ple, in 2013 Roland Koehler wrote a good Python-​oriented arti­cle on sev­er­al con­sid­er­a­tions and the dif­fer­ent approach­es avail­able. Although he end­ed up devel­op­ing his own (quelle sur­prise), he makes a good case that a tem­plate proces­sor ought to at least pro­vide var­i­ous log­ic con­structs as well as embed­ded expres­sions, if not a full pro­gram­ming lan­guage. Koehler specif­i­cal­ly warns against the lat­ter, though, as a tem­plate devel­op­er might change an application’s data mod­el, to say noth­ing of the pos­si­bil­i­ty of exe­cut­ing arbi­trary destruc­tive code.

    I can appre­ci­ate this rea­son­ing. I’ve suc­cess­ful­ly used Perl tem­plate proces­sors like the afore­men­tioned Template::Toolkit (which has both log­ic direc­tives and an option­al facil­i­ty for eval­u­at­ing Perl code) and Text::Xslate (which sup­ports sev­er­al tem­plate syn­tax­es includ­ing a sub­set of Template::Toolkits, but with­out the abil­i­ty to embed Perl code). We use the lat­ter at work com­bined with Text::Xslate::Bridge::TT2Likes emu­la­tion of var­i­ous Template::Toolkit vir­tu­al meth­ods and it’s served us well.

    But using those mod­ules’ DSLs means more sophis­ti­cat­ed tasks need extra time and effort find­ing the cor­rect log­ic and expres­sions. This also assumes that their designer(s) have antic­i­pat­ed my needs either through built-​in fea­tures or exten­sions. I’m already writ­ing Perl; why should I switch to anoth­er, more lim­it­ed lan­guage and envi­ron­ment pro­vid­ed I can remain dis­ci­plined enough to avoid issues like those described above by Koehler?

    So for my per­son­al projects, I favor tem­plate proces­sors that use the full pow­er of the Perl lan­guage like Mojolicious’ embed­ded Perl ren­der­er or the ven­er­a­ble Text::Template for non-​web appli­ca­tions. It saves me time and I’ll like­ly want more than any DSL can pro­vide. This may not apply to your sit­u­a­tion, though, and I’m open to counter-arguments.

    What’s your favorite tem­plate proces­sor and why? Let me know in the comments.

  • VPS upgrade

    If you can see this, then the site’s upgrade from HostGator shared host­ing to VPS and the sub­se­quent Cloudflare DNS change is com­plete. Thank you for fly­ing with us.

  • Speaking at Developer Career Day 2021

    I’ll be run­ning a work­shop based on my recent Blogging Outside the Bubble webi­nar dur­ing Developer Career Day 2021, which is run­ning from August 6 through 7. It’s free to reg­is­ter, with top speak­ers and prac­ti­cal career discussions.

    Find out more and reg­is­ter here!

  • Gradual method renaming in Perl

    Gradual method renaming in Perl

    We have a huge code­base of over 700,000 lines of Perl spread across a cou­ple dozen Git repos­i­to­ries at work. Sometimes refac­tor­ing is easy if the class­es and meth­ods involved are con­fined to one of those repos, but last week we want­ed to rename a method that was poten­tial­ly used across many of them with­out hav­ing to QA and launch so many changes. After get­ting some help from Dan Book and Ryan Voots on the #perl libera.chat IRC chan­nel, I arrived at the fol­low­ing solution.

    First, if all you want to do is alias the new method call to the old while mak­ing the least amount of changes, you can just do this:

    *new_method = \&old_method;

    This takes advan­tage of Perl’s type­globs by assign­ing to the new method­’s name in the sym­bol table a ref­er­ence (indi­cat­ed by the \ char­ac­ter) to the old method. Methods are just sub­rou­tines in Perl, and although you don’t need the & char­ac­ter when call­ing one, you do need it if you’re pass­ing a sub­rou­tine as an argu­ment or cre­at­ing a ref­er­ence, as we’re doing above.

    I want­ed to do a bit more, though. First, I want­ed to log the calls to the old method name so that I could track just how wide­ly it’s used and have a head start on renam­ing it else­where in our code­base. Also, I did­n’t want to fill our logs with those calls—we have enough noise in there already. And last­ly, I want­ed future calls to go direct­ly to the new method name with­out adding anoth­er stack frame when using caller or Carp.

    With all that in mind, here’s the result:

    sub old_method {
        warn 'old_method is deprecated';
        no warnings 'redefine';
        *old_method = \&new_method;
        goto &new_method;
    }
    
    sub new_method {
        # code from old_method goes here
    }

    Old (and not-​so-​old) hands at pro­gram­ming are prob­a­bly leap­ing out of their seats right now yelling, YOU’RE USING GOTO! GOTO IS CONSIDERED HARMFUL!” And they’re right, but this isn’t Dijkstra’s goto. From the Perl manual:

    The goto &NAME form is quite dif­fer­ent from the oth­er forms of goto. In fact, it isn’t a goto in the nor­mal sense at all, and does­n’t have the stig­ma asso­ci­at­ed with oth­er gotos. Instead, it exits the cur­rent sub­rou­tine (los­ing any changes set by local) and imme­di­ate­ly calls in its place the named sub­rou­tine using the cur­rent val­ue of @_. […] After the goto, not even caller will be able to tell that this rou­tine was called first.

    perl­func man­u­al page

    Computer sci­en­tists call this tail call elim­i­na­tion. The bot­tom line is that this achieves our third goal above: imme­di­ate­ly jump­ing to the new method as if it were orig­i­nal­ly called.

    The oth­er tricky bit is in the line before, when we’re redefin­ing old_method to point to new_method while we’re still inside old_method. (Yes, you can do this.) If you’re run­ning under use warnings (and we are, and you should), you first need to dis­able that warn­ing. Later calls to old_method will go straight to new_method with­out log­ging anything.

    And that’s it. The next step after launch­ing this change is to add a sto­ry to our back­log to mon­i­tor our logs for calls to the old method, and grad­u­al­ly refac­tor our oth­er repos­i­to­ries. Then we can final­ly remove the old method wrapper.