MooX::Struct
2024-12-22 perl OOP class Moo MooX::Struct Function::Parameters MooX::StrictConstructor namespace::clean Type::Tiny Types::StandardWhen I need a class in perl, I usually reach for Moo and several other companion classes. My usual boilerplate is something like this:
package Direction;
use Moo;
use Types::Standard qw(Int);
use Function::Parameters;
use namespace::clean;
use MooX::StrictConstructor;
has dx => (is => 'ro', isa => Int, required => 1);
has dy => (is => 'ro', isa => Int, required => 1);
method up($class:) {
return Direction->new(dx => 0, dy => -1);
}
method clockwise() {
# 90° clockwise rotation: (x,y) becomes (y,−x)
return Direction->new(dx => -$self->dy, dy => $self->dx);
}
method opposite() {
return Direction->new(dx => -$self->dx, dy => -$self->dy);
}
method to_string() {
return join ',', $self->dx, $self->dy;
}
1;
Few notes and rationale for above:
-
I started making classes with Moose, but later found I don’t need the meta-protocol very often and Moo is usually more than enough for my needs. It has also much smaller memory footprint and it is very fast to load and run
-
Type::Tiny and Types::Standard are wonderful for type constraints, even pretty complicated ones like
HashRef[ArrayRef[InstanceOf['Point']]]
. Moo accepts them for property type checking, but they can be also used standalone or with Function::Parameters. They also have means to coerce the value into the type, allowing to “inflate” plain values into classes and similar -
Function::Parameters provide nice and fast (see my benchmark) implementation for parameters, unpacking
$self
, or building class methods. They also support named parameters, defaults, and type constraints using Type::Tiny -
namespace::clean removes imported functions from the class interface. This means
has
, type constraints, or anything used internally -
MooX::StrictConstructor prevents typos in constructor usage and make sure only supported properties are supplied. Unfortunately it needs to be specified after the namespace::clean, because of this problem
-
Also note the factory method
up
to build the object some custom way. I often have something likefrom_string
orfrom_xml
to build the class
Now this provides many nice features and good way to grow, but feels a bit over when you just need small storage class. Here the MooX::Struct can be of use:
use MooX::Struct
Empty => ['length'],
Data => ['id', 'length'],
Block => ['id', 'pos'],
;
my @array = (
Empty->new(length => 5),
Data->new(length => 5, id => 125),
Data->new(length => 8, id => 129),
Empty->new(length => 3),
Data->new(length => 8, id => 172),
);
It creates three small Moo-based classes with read-only attributes defined as specified. This is really basic usage, there are plenty of options how to override this behavior, inherit other structs, use roles, add methods and such. But in larger usage the boilerplate is not that big deal, so I usually only use it for small things.