Cutting the fat: Lightweight Perl OO modules
Tags: perl, oop, cpan, accessors
Published: 2021-09-07 19:25:48

---
title: Cutting the fat: Lightweight Perl OO modules
published: true
date: 2021-09-07 14:00:00 UTC
tags: Perl,OOP,CPAN,accessors
canonical_url: https://phoenixtrap.com/2021/09/07/cutting-the-fat-lightweight-perl-oo/
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b0pi48yphiyvrow6yd1g.jpeg
---

This blog has devoted a fair amount of attention to the popular and multifaceted object-oriented system [Moose](https://phoenixtrap.com/tag/moose/) and its lightweight subset [Moo](https://phoenixtrap.com/tag/moo/). I’ve also covered [Object::Pad](https://phoenixtrap.com/tag/objectpad/), the testbed of concepts and syntax for [Corinna](https://phoenixtrap.com/tag/corinna/), the [proposed](https://github.com/Ovid/Cor/wiki#the-proposal) next-generation Perl core OO system. But what if your project is too memory‑, performance‑, or dependency-constrained for these options?

It turns out that [CPAN](https://www.cpan.org/) has a rich history of lighter-weight OO modules to meet many different needs. If you can live with their trade-offs, they’re worth investigating instead of rolling your own layer over [Perl’s OO](https://perldoc.perl.org/perlobj). Here are a few.

## [Class::Struct](https://perldoc.perl.org/Class::Struct)

[Class::Struct](https://perldoc.perl.org/Class::Struct)’s main claim to fame is its inclusion in the standard Perl distribution, so there’s no need to install dependencies from CPAN. It provides a syntax for defining classes as [C‑style `struct`s](https://en.wikipedia.org/wiki/Struct_(C_programming_language)) at either [compile time](https://perldoc.perl.org/Class::Struct#Class-Creation-at-Compile-Time) or runtime. (There’s no speed advantage to the former; it just means that your class will be built as if you had written the accessors yourself as [`sub`](https://perldoc.perl.org/functions/sub)s.) Here’s an example:

```perl
#!/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 supports accessors for scalar, array, and hash types, as well as other classes (not demonstrated). Consult [the module’s documentation](https://perldoc.pl/Class::Struct#Element-Types-and-Accessor-Methods) for the different ways to define and retrieve them.

## [Class::Accessor](https://metacpan.org/pod/Class::Accessor)

[Class::Accessor](https://metacpan.org/pod/Class::Accessor) does one thing: it makes accessors and mutators (also known as getters and setters) for fields in your class. Okay, it actually does another thing: it provides your class with a new method to initialize those fields. Those accessors can be [read-write](https://metacpan.org/pod/Class::Accessor#mk_accessors), [read-only](https://metacpan.org/pod/Class::Accessor#mk_ro_accessors), or [write-only](https://metacpan.org/pod/Class::Accessor#mk_wo_accessors). (Why would you want write-only accessors?) You can define any of them using either its historical class methods or a [Moose-like attribute syntax](https://metacpan.org/pod/Class::Accessor#Moose!).

If you’re trying to squeeze every bit of performance out of your code and can sacrifice a little flexibility in altering accessor behavior, you can opt for [Class::Accessor::Fast](https://metacpan.org/pod/Class::Accessor::Fast) or [Class::Accessor::Faster](https://metacpan.org/pod/Class::Accessor::Faster). The former still uses hash references under the hood to represent objects and the latter uses array references. The main Class::Accessor documentation contains an [efficiency comparison of the three](https://metacpan.org/pod/Class::Accessor#EFFICIENCY) for your edification.

Here’s an example script using Class::Accessor::Faster and the Moose-like syntax:

```perl
#!/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](https://metacpan.org/pod/Class::Tiny)

[Class::Tiny](https://metacpan.org/pod/Class::Tiny) both does less and more than Class::Accessor. All of its generated accessors are read-write, but you can also give their attributes lazy defaults. Its generated constructor takes arguments via either a Class::Accessor-style hash reference or a plain list of key/value pairs, so that’s a little more convenient. It also supports Moose-style [`BUILDARGS`](https://metacpan.org/pod/Class::Tiny#Object-construction), [`BUILD`](https://metacpan.org/pod/Class::Tiny#BUILD), and [`DEMOLISH`](https://metacpan.org/pod/Class::Tiny#DEMOLISH) methods for argument adjustment, validation, and object cleanup, respectively.

It’s a toss-up as to which of the previous two is “better.” You’ll have to examine their respective features and determine which ones map to your needs.

Here’s an example script that shows a few of Class::Tiny’s unique features:

```perl
#!/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](https://metacpan.org/pod/Object::Tiny)

For an even more minimalist approach, consider [Object::Tiny](https://metacpan.org/pod/Object::Tiny). Its accessors are read-only, it gives you a simple constructor, and that’s it. Its documentation lists [a number of reasons](https://metacpan.org/pod/Object::Tiny#Why-bother-when-Class::Accessor::*-already-does-the-same-thing?) why it can be superior to Class::Accessor, including lower memory usage and less typing. There’s also a fork called [Object::Tiny::RW](https://metacpan.org/pod/Object::Tiny::RW) that adds read-write support to its accessors.

Class::Tiny’s documentation contains a [feature table comparison](https://metacpan.org/pod/Class::Tiny#Why-this-instead-of-Object::Tiny-or-Class::Accessor-or-something-else?) of it, Object::Tiny, and Class::Accessor. This may help you decide which to use.

Here’s an example script:

```perl
#!/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 requiring a C compiler to install them, there are variants that use [Perl’s XS interface](https://perldoc.perl.org/perlxs) instead of pure Perl code:

- [Class::XSAccessor](https://metacpan.org/pod/Class::XSAccessor)
- [Class::XSAccessor::Compat](https://metacpan.org/pod/Class::XSAccessor::Compat)
- [Object::Tiny::XS](https://metacpan.org/pod/Object::Tiny::XS)
- [Object::Tiny::RW::XS](https://metacpan.org/pod/Object::Tiny::RW::XS)

## Roles with [Role::Tiny](https://metacpan.org/pod/Role::Tiny)

If you’re eyeing Moose and Moo’s support for [roles](https://metacpan.org/dist/Moose/view/lib/Moose/Manual/Roles.pod) (also known as [traits](https://en.m.wikipedia.org/wiki/Trait_(computer_programming))) as an alternative to inheritance but still want to keep things light with one of the above modules, you’re in luck. The [Role::Tiny](https://metacpan.org/pod/Role::Tiny) module lets you compose methods into consuming classes with Moo-like syntax and will pull in [Common Lisp Object System](https://en.wikipedia.org/wiki/Common_Lisp_Object_System)-style [method modifier](https://metacpan.org/pod/Class::Method::Modifiers#DESCRIPTION) support from [Class::Method::Modifiers](https://metacpan.org/pod/Class::Method::Modifiers) if you need it. It does mean another couple of CPAN dependencies, so if that’s a problem in your situation you’ll just have to live without roles.

Here’s an example script with a role and a consuming class that uses Class::Tiny. The role requires that its consumers implement a `required_method`, provides a `foo` method that uses it, and a method modifier for `bar`.

```perl
#!/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 writing everything longhand, but modules like these can save a lot of time and typing as well as reduce errors. Do you have a favorite, maybe something I missed? Let me know in the comments.