woman in black tank top and blue denim jeans

This blog has devot­ed a fair amount of atten­tion to the pop­u­lar and mul­ti­fac­eted object-​oriented sys­tem Moose and its light­weight sub­set Moo. I’ve also cov­ered Object::Pad, the test­bed of con­cepts and syn­tax for Corinna, the pro­posed next-​generation Perl core OO sys­tem. But what if your project is too memory‑, performance‑, or dependency-​constrained for these options?

It turns out that CPAN has a rich his­to­ry of lighter-​weight OO mod­ules to meet many dif­fer­ent needs. If you can live with their trade-​offs, they’re worth inves­ti­gat­ing instead of rolling your own lay­er over Perl’s OO. Here are a few.

Class::Struct

Class::Structs main claim to fame is its inclu­sion in the stan­dard Perl dis­tri­b­u­tion, so there’s no need to install depen­den­cies from CPAN. It pro­vides a syn­tax for defin­ing class­es as C‑style structs at either com­pile time or run­time. (There’s no speed advan­tage to the for­mer; it just means that your class will be built as if you had writ­ten the acces­sors your­self as subs.) Here’s an example:

#!/usr/bin/env perl

use v5.24; # for strict, say, and postfix dereferencing
use warnings;

package Local::MyClass;
use Class::Struct (
    foo => '$',
    bar => '@',
    baz => '%',
);

package main;

my $obj = Local::MyClass->new(
    foo => 'hello',
    bar => [1, 2, 3],
    baz => { name => 'Mark'},
);

say $obj->foo, ' ', $obj->baz('name');
say join ',', $obj->bar->@*;

# replace the name element of baz
$obj->baz(name => 'Sharon');

# replace the second element of bar
$obj->bar(1, 'replaced');
say $obj->foo, ' ', $obj->baz('name');
say join ',', $obj->bar->@*;

And here’s the output:

hello Mark
1,2,3
hello Sharon
1,replaced,3

Note that Class::Struct sup­ports acces­sors for scalar, array, and hash types, as well as oth­er class­es (not demon­strat­ed). Consult the module’s doc­u­men­ta­tion for the dif­fer­ent ways to define and retrieve them.

Class::Accessor

Class::Accessor does one thing: it makes acces­sors and muta­tors (also known as get­ters and set­ters) for fields in your class. Okay, it actu­al­ly does anoth­er thing: it pro­vides your class with a new method to ini­tial­ize those fields. Those acces­sors can be read-​write, read-​only, or write-​only. (Why would you want write-​only acces­sors?) You can define any of them using either its his­tor­i­cal class meth­ods or a Moose-​like attribute syn­tax.

If you’re try­ing to squeeze every bit of per­for­mance out of your code and can sac­ri­fice a lit­tle flex­i­bil­i­ty in alter­ing acces­sor behav­ior, you can opt for Class::Accessor::Fast or Class::Accessor::Faster. The for­mer still uses hash ref­er­ences under the hood to rep­re­sent objects and the lat­ter uses array ref­er­ences. The main Class::Accessor doc­u­men­ta­tion con­tains an effi­cien­cy com­par­i­son of the three for your edification.

Here’s an exam­ple script using Class::Accessor::Faster and the Moose-​like syntax:

#!/usr/bin/env perl

use v5.12; # for strict and say
use warnings;

package Local::MyClass;
use Class::Accessor::Faster 'moose-like';

has readwrite => (is => 'rw');
has readonly  => (is => 'ro');

package main;

my $obj = Local::MyClass->new( { # must be a hash reference
    readwrite => 'hello',
    readonly  => 'world',
} );

say $obj->readwrite, ' ', $obj->readonly;
$obj->readwrite('greetings');
say $obj->readwrite, ' ', $obj->readonly;

# throws an error
$obj->readonly('Cleveland');

And here is its output:

hello world
greetings world
'main' cannot alter the value of 'readonly' on objects of class 'Local::MyClass' at ./caf.pl line 24.

Class::Tiny

Class::Tiny both does less and more than Class::Accessor. All of its gen­er­at­ed acces­sors are read-​write, but you can also give their attrib­ut­es lazy defaults. Its gen­er­at­ed con­struc­tor takes argu­ments via either a Class::Accessor-style hash ref­er­ence or a plain list of key/​value pairs, so that’s a lit­tle more con­ve­nient. It also sup­ports Moose-​style BUILDARGS, BUILD, and DEMOLISH meth­ods for argu­ment adjust­ment, val­i­da­tion, and object cleanup, respectively.

It’s a toss-​up as to which of the pre­vi­ous two is bet­ter.” You’ll have to exam­ine their respec­tive fea­tures and deter­mine which ones map to your needs.

Here’s an exam­ple script that shows a few of Class::Tiny’s unique features:

#!/usr/bin/env perl

use v5.12; # for strict and say
use warnings;

package Local::MyClass;
use Class::Tiny qw<foo bar>,
{
    baz       => 'default baz',
    timestamp => sub { time },
};

package main;

my $obj = Local::MyClass->new( # plain key-values OK
    foo => 'hello',
    bar => 'world',
);

say $obj->foo, ' ', $obj->bar;
say 'Object built on ', scalar localtime $obj->timestamp;
$obj->foo('greetings');
$obj->bar('Cleveland');
say $obj->foo, ' ', $obj->bar;
say $obj->baz;

And its output:

hello world
Object built on Tue Sep  7 09:00:00 2021
greetings Cleveland
default baz

Object::Tiny

For an even more min­i­mal­ist approach, con­sid­er Object::Tiny. Its acces­sors are read-​only, it gives you a sim­ple con­struc­tor, and that’s it. Its doc­u­men­ta­tion lists a num­ber of rea­sons why it can be supe­ri­or to Class::Accessor, includ­ing low­er mem­o­ry usage and less typ­ing. There’s also a fork called Object::Tiny::RW that adds read-​write sup­port to its accessors.

Class::Tiny’s doc­u­men­ta­tion con­tains a fea­ture table com­par­i­son of it, Object::Tiny, and Class::Accessor. This may help you decide which to use.

Here’s an exam­ple script:

#!/usr/bin/env perl

use v5.12; # for strict and say
use warnings;

package Local::MyClass;
use Object::Tiny qw<foo bar>;

package main;

my $obj = Local::MyClass->new(
    foo => 'hello',
    bar => 'world',
);

say $obj->foo, ' ', $obj->bar;

# has no effect unless you use Object::Tiny::RW
$obj->foo('greetings');
say $obj->foo, ' ', $obj->bar;

And its output:

hello world
hello world

Add some speed with XS

If the above options are still too slow and you don’t mind requir­ing a C com­pil­er to install them, there are vari­ants that use Perl’s XS inter­face instead of pure Perl code:

Roles with Role::Tiny

If you’re eye­ing Moose and Moo’s sup­port for roles (also known as traits) as an alter­na­tive to inher­i­tance but still want to keep things light with one of the above mod­ules, you’re in luck. The Role::Tiny mod­ule lets you com­pose meth­ods into con­sum­ing class­es with Moo-​like syn­tax and will pull in Common Lisp Object System-style method mod­i­fi­er sup­port from Class::Method::Modifiers if you need it. It does mean anoth­er cou­ple of CPAN depen­den­cies, so if that’s a prob­lem in your sit­u­a­tion you’ll just have to live with­out roles.

Here’s an exam­ple script with a role and a con­sum­ing class that uses Class::Tiny. The role requires that its con­sumers imple­ment a required_method, pro­vides a foo method that uses it, and a method mod­i­fi­er for bar.

#!/usr/bin/env perl

use v5.12; # for strict and say
use warnings;

package Local::MyRole;
use Role::Tiny;

requires 'required_method';

sub foo {
    my $self = shift;
    say $self->required_method();
}

before bar => sub {
    warn 'About to call bar...';
};

package Local::MyClass;
use Class::Tiny {name => ''};
use Role::Tiny::With;
with 'Local::MyRole';

sub bar {
    my ($self, $greeting) = @_;
    say "$greeting ", $self->name;
}

sub required_method {
    my $self = shift;
    return 'Required by Local::MyRole';
}

package main;

my $obj = Local::MyClass->new(name => 'Mark');
$obj->bar('hello');

$obj->name('Sharon');
$obj->bar('salutations');

$obj->foo();

And its output:

About to call bar... at ./rt.pl line 17.
hello Mark
About to call bar... at ./rt.pl line 17.
salutations Sharon
Required by Local::MyRole

What’s your favorite?

There will always be those who insist on writ­ing every­thing long­hand, but mod­ules like these can save a lot of time and typ­ing as well as reduce errors. Do you have a favorite, maybe some­thing I missed? Let me know in the comments.

7 thoughts on “Cutting the fat: Lightweight Perl OO modules

    • I am hes­i­tant to rec­om­mend Mo because it seems like more of a golf­ing hack than any­thing else. It also has not seen updates since 2016 and there are a num­ber of (to my eye) crit­i­cal unad­dressed issues on GitHub. If it works for you, great.

      • I also would­n’t see it as seri­ous con­tender for your list. Lazy as I am, I just use it to avoid fat­pack­ing all the oth­er­wise need­ed pack­ages in one script for my col­leagues to use or for dis­trib­ut­ing it to alot of machines in use, all with perl (yay!), but an ancient one (5.8.8, sad­nois­es). Inline-​Mo solved much of that for me, and if code from those scripts has to be recy­cled for big­ger /​more long-​term solu­tions -> upgrade to Moo(se) is easy.

        But it seems in the past I just glossed over Class::Struct and for­got about it. Since it’s includ­ed — even in 5.8.8 — maybe that’s an alter­na­tive, and with­out col­leagues being sus­pi­cious about the strange code at the begin­ning of the script (inlined Mo).

        Anyway, nice overview, and thx for remind­ing me of the bat­ter­ies already included!

  1. If you’re look­ing for your objects to them­selves be light­weight, check out TOBYINK’s Monjon based on Moo but using a blessed scalar ref­er­ence instead of a hashref, sup­port­ing com­plex attrib­ut­es inside-out

    https://metacpan.org/pod/Monjon

    • What I’m learn­ing is that dif­fer­ent peo­ple have dif­fer­ent ideas on what light­weight” means. Does it mean mem­o­ry? Storage? Speed? Features? Dependencies? In Monjon’s case it’s all about sav­ing mem­o­ry so long as your attrib­ut­es are small. But it’s slow­er than Moo and actu­al­ly depends on it. So it’s all about trade­offs: what are you will­ing to sac­ri­fice in order to opti­mize for some­thing else?

Comments are closed.