Tag: OOP

  • Privacy and speed with Perl’s Object::Pad

    Privacy and speed with Perl’s Object::Pad

    Last week found me explor­ing Object::Pad as an alter­na­tive to the Moo object-​oriented frame­work for Perl since the for­mer is pro­to­typ­ing the syn­tax and con­cepts for a pro­posed built-​in OO frame­work named Corinna. I had to put that par­tic­u­lar project on hold as dbcrit­ics cur­rent design is a bit too role-​happy and Object::Pad cur­rent­ly lacks method mod­i­fiers as in Moo. (Corinna is explic­it­ly skip­ping them for its cur­rent min­i­mum viable prod­uct.) Thankfully, devel­op­ment con­tin­ues at a rapid pace. For instance, author Paul Evans has already addressed a prob­lem I ran into when attempt­ing to exam­ine slot val­ues in the debugger.

    But I want­ed to high­light a point I made in one of the com­ments last week: Object::Pad’s slots (a.k.a. fields, attrib­ut­es, what­ev­er) are pri­vate by default, com­plete­ly unex­posed to oth­er class instances unless they mon­key with the meta-​object pro­to­col. Unless you explic­it­ly define or gen­er­ate some kind of acces­sor method, these slots act like lex­i­cal (a.k.a. my) vari­ables and are only avail­able to meth­ods with­in the class.

    Here’s an example:

    use v5.14; # for say and package blocks
    use Object::Pad 0.50;
    use Feature::Compat::Try;
    
    class Local::MyClass {
        has $arg           :param  = 'hello';
        has $readable_slot :reader = 'world';
        has $private_slot          = 'shh';
    
        method show_slots {
            say "You passed me $arg in the constructor.";
            say "I can see $readable_slot and you can use it as a reader.";
            say "Here's me using the reader too: ", $self->readable_slot;
            say "But only I can see $private_slot.";
            return;
        }
    }
    
    package main {
        my $obj = Local::MyClass->new(arg => 'foo');
        $obj->show_slots();
        say $obj->readable_slot;
    
        # Nope: Not a HASH reference
        try { say $obj->{private_slot} } catch ($e) { say "Nope: $e" }
    
        # Nope: Can't locate object method "private_slot" via package "Local::MyClass"
        try { say $obj->private_slot } catch ($e) { say "Nope: $e" }
    }

    This stands in stark con­trast to Perl’s more low-​tech hashref-​based objects, where all attrib­ut­es are avail­able sim­ply through deref­er­enc­ing the instance, e.g., $object->{foo}. Although dis­cour­aged, OO purists some­times ding Perl for this kind of unen­forced encap­su­la­tion, and I myself have seen code­bas­es that vio­late it despite the con­ven­tion of pre­ced­ing pri­vate method and attribute names with an under­score (_).

    Unfortunately, there is not yet any way to declare an Object::Pad method pri­vate. You could use lex­i­cal sub­rou­tines, but then you lose the con­ve­nience of a pre-​made $self vari­able and acces­si­bil­i­ty through the MOP. The Corinna pro­pos­al lists sev­er­al dif­fer­ent types of meth­ods includ­ing pri­vate ones, so maybe this is an area for future Object::Pad development.

    Another open ques­tion from the com­ments: How is [Object::Pad] on mem­o­ry and speed com­pared to Moo and blessed objects?” Luckily the pro­lif­ic per­lan­car has already added Object::Pad to his Bencher::Scenarios::Accessors dis­tri­b­u­tion, and from that, it appears that between it and Moo, Object::Pad is faster on start­up, neck-​and-​neck on object con­struc­tion and acces­sor gen­er­a­tion, and slow­er on reads and writes. (Note that Object::Pad is a fast-​moving tar­get so these fig­ures may not track with the lat­est ver­sion’s changes.) It’s no sur­prise that plain blessed objects fared bet­ter than both in most sce­nar­ios except for reads, where Moo was faster than hash-​based objects but slow­er than array-based.

    I expect that should Corinna be built into Perl it would nar­row that gap with blessed objects, but in my mind, the advan­tages of using an object sys­tem out­weigh the per­for­mance hit 95% of the time. As far as bench­mark­ing mem­o­ry goes, I still need to test that on a Linux box (maybe my new VPS?) once I get more famil­iar with the Bencher framework.

  • What’s Next for Object-​Oriented Perl?

    What’s Next for Object-​Oriented Perl?

    Introduction: The current state of play

    Perl has very min­i­mal” sup­port for object-​oriented (OO) pro­gram­ming out of the box by its own admis­sion. It’s class-​based but class­es are just pack­ages used dif­fer­ent­ly. Objects are just data struc­tures blessed into a class, meth­ods are just sub­rou­tines whose first argu­ment is an object or class name, and attributes/​properties are often just the key-​value pair of a hash stored in the object. (This last is a fea­ture shared with JavaScript, whose prototype-​based objects are just col­lec­tions of key-​value pairs with the keys addressed as prop­er­ties.) You’ve got poly­mor­phism, inher­i­tance, and it’s up to you to enforce encap­su­la­tion.

    This can take a lot of work to use effec­tive­ly. To help address that, sev­er­al sys­tems have been devel­oped over the years to reduce boil­er­plate and pro­vide mod­ern (or post­mod­ern”) OO fea­tures that devel­op­ers from oth­er lan­guages expect. My favorite for a while has been Moo: it’s got the fea­tures I need 90% of the time like built-​in con­struc­tors, roles (an alter­na­tive to com­po­si­tion through inher­i­tance), attrib­ut­es, type val­i­da­tion, and method mod­i­fiers for enhanced poly­mor­phism. And if I need to dig around in the guts of class­es, attrib­ut­es, and the like I can always upgrade to Moo’s big broth­er Moose and its meta-​object pro­to­col with min­i­mal effort.

    Corinna, Object::Pad, and porting dbcritic

    But there’s a new kid on the block. Curtis Ovid” Poe has been spear­head­ing Corinna, an effort to bring effec­tive OO to the Perl core and leapfrog [empha­sis his] the capa­bil­i­ties of many OO lan­guages today.” No CPAN mod­ules, no chain of depen­den­cies; just sol­id OO fea­tures and syn­tax built-​in. And while Corinna is a ways off from ship­ping, Paul LeoNerd” Evans (maybe I should get a cool nick­name too?) has been imple­ment­ing some of these ideas as new Perl key­word syn­tax in his Object::Pad module.

    Both Ovid and LeoNerd have been ask­ing devel­op­ers to try out Object::Pad, not just as a new toy, but to get feed­back on what works and what needs to be added. So I thought I’d try port­ing an old­er small Moo-​based project named dbcrit­ic to this new real­i­ty. In the process, I learned some of the advan­tages and dis­ad­van­tages of work­ing with Object::Pad. Hopefully, this can inform both it and Corinna’s evo­lu­tion as well as oth­er curi­ous devel­op­ers’ eval­u­a­tions. You can fol­low my cod­ing efforts in this GitHub branch.

    First, the mar­quee result: the code for App::DBCritic (the class I start­ed with) is clean­er and short­er, with 33 lines shaved off so far. Mainly this is due to Object::Pad’s more con­cise attribute syn­tax (called slots” in its doc­u­men­ta­tion) and lack of explic­it sup­port for Moo’s attribute coer­cion. I only used the lat­ter for one attribute in the Moo ver­sion and I’m not sure it worked par­tic­u­lar­ly well, so it was­n’t hard to jet­ti­son. But if your code sup­ports coer­cions exten­sive­ly, you’ll have to look into Object::Pad’s BUILD or ADJUST phase blocks for now.

    Before, a Moo attribute with var­i­ous options:

    has schema => (
        is        => 'ro',
        coerce    => 1,
        lazy      => 1,
        default   => \&_build_schema,
        coerce    => \&_coerce_schema,
        predicate => 1,
    );

    After, an Object::Pad slot. No coer­cion and builder code is han­dled in a lat­er ADJUST block:

    has $schema :reader :param = undef;

    Speaking of ADJUST blocks, it took a lit­tle bit of insight from the #perl IRC chan­nel to real­ize that they were the appro­pri­ate place for set­ting slot defaults that are com­put­ed from oth­er slots. Previously I was using a maze of depen­den­cies mix­ing Moo lazy attrib­ut­es and builder meth­ods. Clarifying the main set of option­al con­struc­tor argu­ments into a sin­gle ADJUST block helped untan­gle things, so this might be an indi­ca­tion that lazy attrib­ut­es are an antipat­tern when try­ing to write clean code. It’s also worth not­ing that Object::Pad ADJUST blocks run on object con­struc­tion, where­as Moo lazy attrib­ut­es are only built when need­ed. This tends to mat­ter for data­base access.

    The ADJUST block for the $schema slot:

    ADJUST {
        my @connect_info = ( $dsn, $username, $password );
        if ($class_name and eval "require $class_name") {
            $schema = $class_name->connect(@connect_info);
        }
        elsif ( not ( blessed($schema) and $schema->isa('DBIx::Class::Schema') ) ) {
            local $SIG{__WARN__} = sub {
                if ( $_[0] !~ / has no primary key at /ms ) {
                    print {*STDERR} $_[0];
                }
            };
            $schema = App::DBCritic::Loader->connect(@connect_info);
        }
        croak 'No schema defined' if not $schema;
    }

    Object::Pad’s slots have one great advan­tage over Moo and Moose attrib­ut­es: they direct­ly sup­port Perl array and hash data struc­tures, while the lat­ter only sup­ports scalars and ref­er­ences con­tained in scalars. This means meth­ods in your class can elim­i­nate a deref­er­enc­ing step, again lead­ing to clean­er code. I used this specif­i­cal­ly in the @violations array and %elements hash slots and was very pleased with the results.

    The @violations and %elements slots and their ADJUST blocks:

    has %elements;
    
    ADJUST {
        %elements = (
            Schema       => [$schema],
            ResultSource => [ map { $schema->source($_) } $schema->sources ],
            ResultSet    => [ map { $schema->resultset($_) } $schema->sources ],
        );
    }
    
    has @violations;
    
    ADJUST {
        @violations = map { $self->_policy_loop( $_, $elements{$_} ) }
            keys %elements;
    }
    
    method violations { wantarray ? @violations : \@violations }
    

    Issues

    I did have some devel­op­ment life­cy­cle issues with Object::Pad, but they’re main­ly a result of its future-​facing syn­tax. I had to give up using perltidy and perlcritic in my build and test phas­es, respec­tive­ly: perltidy does­n’t under­stand slot attrib­ut­es like :reader and :param and will emit an error file (but code still com­piles), and sev­er­al of the perlcritic poli­cies I use report prob­lems because its PPI pars­er does­n’t rec­og­nize the new syn­tax. I could add excep­tions in the perlcriticrc file and lit­ter my code with more ## no critic anno­ta­tions than it already had, but at this point, it was eas­i­er to just dis­able it entirely.

    Another thing I had to dis­able for now was my Dist::Zilla::Plugin::Test::UnusedVars-gen­er­at­ed Test::Vars test for detect­ing unused vari­ables, as it reports mul­ti­ple fail­ures for the hid­den @(Object::Pad/slots) vari­able. It does have options for ignor­ing cer­tain vari­ables, though, so I can explore using those and pos­si­bly file a pull request to ignore that vari­able by default.

    Conclusion: The future looks bright

    Overall I’m sat­is­fied with Object::Pad and by exten­sion some of the syn­tax that Corinna will intro­duce. I’m going to try port­ing the rest of dbcrit­ic and see if I can work around the issues I list­ed above with­out giv­ing up the kwali­tee improve­ment tools I’m used to. I’ll post my find­ings if I feel it mer­its anoth­er blog.

    What do you think? Is this the future of object-​oriented Perl? Let me know in the com­ments below.

  • Taming the Moose: Picking the best way to subclass Perl methods

    Taming the Moose: Picking the best way to subclass Perl methods

    The override key­word in Perl’s Moose object sys­tem is a nice bit of code-​as-​documentation since it explic­it­ly states that a giv­en method over­rides from its super­class. It also has a super key­word that can be used inside an override, call­ing the next most appro­pri­ate super­class method with the same argu­ments as the orig­i­nal method.”

    The Moose doc­u­men­ta­tion then goes on to say, The same thing can be accom­plished with a nor­mal method call and the SUPER:: pseudo-​package; it is real­ly your choice.” So when should you use one and not the oth­er? I decid­ed to find out.

    First I defined a sim­ple Moose super­class with a sin­gle method:

    package Local::MyClass;
    
    use Moose;
    
    sub my_method {
        return blessed $_[0];
    }
    
    __PACKAGE__->meta->make_immutable();
    
    1;

    And then a pair of sub­class­es, one using Moose’s override key­word and one with a plain sub:

    package Local::MyClass::MyChildOverride;
    
    use Moose;
    extends 'Local::MyClass';
    
    override my_method => sub {
        my $self = shift;
        return 'child ' . super;
    };
    
    __PACKAGE__->meta->make_immutable();
    
    1;
    package Local::MyClass::MyChildPlain;
    
    use Moose;
    extends 'Local::MyClass';
    
    sub my_method {
        my $self = shift;
        return 'child ' . $self->SUPER::my_method();
    }
    
    __PACKAGE__->meta->make_immutable();
    
    1;

    So far so good, and both can be called successfully:

    $ perl -Ilib -MLocal::MyClass::MyChildPlain \
      -MLocal::MyClass::MyChildOverride \
      -E '$PREFIX = "Local::MyClass::MyChild";
      for ( qw(Plain Override) ) {
        $object = "$PREFIX$_"->new();
        say $object->my_method()
      }'
    child Local::MyClass::MyChildPlain
    child Local::MyClass::MyChildOverride

    Let’s toss in a new wrin­kle, though. What if we for­got to define the method in the superclass?

    package Local::MyClassNoMethod;
    
    use Moose;
    
    __PACKAGE__->meta->make_immutable();
    
    1;

    Both ways of call­ing the super­class’s method will bug out, of course, but unlike a plain over­ride Moose will actu­al­ly pre­vent you from useing the offend­ing sub­class dur­ing the BEGIN phase:

    $ perl -Ilib -MLocal::MyClassNoMethod::MyChildOverride \
      -E ''
    You cannot override 'my_method' because it has no super method at /Users/mgardner/.plenv/versions/5.34.0/lib/perl5/site_perl/5.34.0/darwin-2level/Moose/Exporter.pm line 419
    	Moose::override('my_method', 'CODE(0x7fe5cb811a88)') called at lib/Local/MyClassNoMethod/MyChildOverride.pm line 9
    	require Local/MyClassNoMethod/MyChildOverride.pm at -e line 0
    	main::BEGIN at lib/Local/MyClassNoMethod/MyChildOverride.pm line 0
    	eval {...} at lib/Local/MyClassNoMethod/MyChildOverride.pm line 0
    Compilation failed in require.
    BEGIN failed--compilation aborted.

    With plain method over­rid­ing, you only get an error if you try to call the super­class’s method. If your over­rid­den method does­n’t do that, it’s per­fect­ly safe to define and call. It’s only if you use that SUPER:: pseudo-​package that things blow up at runtime:

    $ perl -Ilib -MLocal::MyClassNoMethod::MyChildPlain \
      -E '$obj = Local::MyClassNoMethod::MyChildPlain->new();
      $obj->my_method()'
    Can't locate object method "my_method" via package "Local::MyClassNoMethod::MyChildPlain" at lib/Local/MyClassNoMethod/MyChildPlain.pm line 8.

    Note that none of this is caught at com­pile time. perl -c will hap­pi­ly com­pile all these class­es and sub­class­es with­out a peep:

    $ find . -name '*.pm' -exec perl -c {} \;
    ./lib/Local/MyClass/MyChildPlain.pm syntax OK
    ./lib/Local/MyClass/MyChildOverride.pm syntax OK
    ./lib/Local/MyClassNoMethod/MyChildPlain.pm syntax OK
    ./lib/Local/MyClassNoMethod/MyChildOverride.pm syntax OK
    ./lib/Local/MyClass.pm syntax OK
    ./lib/Local/MyClassNoMethod.pm syntax OK

    So what can we con­clude? Moose’s override is a good way of describ­ing your intent with a sub­class, and it will catch you out if you try to use it with­out a cor­re­spond­ing method in a super­class. It is a non-​standard key­word though, so syntax-​highlighting edi­tors and code analy­sis tools won’t rec­og­nize it unless taught. Further, if your sub­class method does­n’t call the same method in a super­class you could even­tu­al­ly get away with remov­ing the lat­ter if you use a plain sub.

    I’ve cre­at­ed a small GitHub project with the sam­ple code from this arti­cle, includ­ing test scripts.

    What do you think? Is override suit­able for your Moose projects, or are you sat­is­fied with plain sub? Let me know in the comments.

  • 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.

  • Perl can escape the Lisp Curse

    Perl can escape the Lisp Curse

    Ten years ago Rudolf Winestock wrote The Lisp Curse, an essay that attempt[ed] to rec­on­cile the pow­er of the Lisp pro­gram­ming lan­guage with the inabil­i­ty of the Lisp com­mu­ni­ty to repro­duce their pre-AI Winter achievements.”

    His con­clu­sion? The pow­er and expres­sive­ness of Lisp have con­spired to keep its devel­op­ers indi­vid­u­al­ly pro­duc­tive, but col­lec­tive­ly unable to orga­nize their work into com­plete, stan­dard­ized, well-​documented, ‑test­ed, and ‑main­tained pack­ages that they could coa­lesce into inter­op­er­a­ble and widely-​adopted solu­tions. Everything from object sys­tems to types to asyn­chro­nous non-​blocking pro­gram­ming and con­cur­ren­cy is up for grabs and has mul­ti­ple com­pet­ing implementations.

    These social effects have doomed Lisp to also-​ran sta­tus in an indus­try where employ­ers much pre­fer that work­ers be fun­gi­ble, rather than max­i­mal­ly pro­duc­tive.” Free tool­ing sup­port has lagged; although Emacs can be hacked end­less­ly to do any­thing, there is no out-​of-​the-​box inte­grat­ed devel­op­ment envi­ron­ment or batteries-​included defaults to imme­di­ate­ly ease new pro­gram­mers into their job.

    Does this all sound famil­iar to Perl developers?

    Perl is renowned for its expres­sive capa­bil­i­ties, enshrined in the TIMTOWTDI (There Is More Than One Way To Do It) design prin­ci­ple. Stories abound of the pro­duc­tiv­i­ty achieved by Perl pro­gram­mers stitch­ing togeth­er mod­ules from CPAN with their own code. Select an object sys­tem (or don’t), maybe throw in an excep­tion han­dler (or don’t), and you too can have a code­base that fel­low devel­op­ers cri­tique for not fol­low­ing their favored tech­niques. Meanwhile, man­agers are strug­gling to fill the rest of the team with new pro­gram­mers look­ing for IDE sup­port and find­ing only a grab-​bag of Vim extensions.

    But there’s hope.

    Perl has start­ed incor­po­rat­ing fea­tures expect­ed of mod­ern pro­gram­ming lan­guages into its core while mak­ing room for fur­ther exper­i­men­ta­tion via CPAN. The Language Server Protocol (from Microsoft of all places!) has enabled Perl IDE fea­tures in text edi­tors to boost pro­duc­tiv­i­ty for new and expe­ri­enced devel­op­ers alike. And there’s a pilot Request For Comment process for fur­ther improvements.

    These efforts point to a future where Perl’s expres­sive strength is mar­ried with sen­si­ble defaults and fea­tures with­out break­ing back­ward com­pat­i­bil­i­ty. Maybe the curse can be overcome.