DISCLAIMER: Image is generated using ChatGPT
.
TL;DR
Class::Mite
gives you Moo
-like syntax (classes, roles, and attributes) with almost zero runtime cost. It’s a thin wrapper over bless
, preserving native Perl
speed while reducing boilerplate and improving readability.
In my previous post, I introduced Class::Mite, a lightweight, dependency-free Class
and Role
system for Perl
. Since then I have added Class::More
that supports basic attributes feature with key has
same as Moo
.
Why Class::Mite?
The purpose of this distribution is to provide something as simple as a bless-based class with zero dependencies and minimal boilerplate. I built it for my pet project which is about 80%
complete and I can’t wait to share it with you all.
From bless to Class::Mite: A Practical Comparison
Perl
’s native object system is famously minimal: you just bless a hash
and you have an object
but as codebases grow, so does the need for structure, readability and safety.
Enter Class::Mite
, a lightweight framework that wraps traditional bless-based
classes with modern conveniences.
It introduces three packages:
Class - for classes with constructors, inheritance
Class::More - everything that Class does with support for attributes
Role - for composable behaviour units
Unlike Moose
or Moo
, Class::Mite aims to stay minimal and fast, remaining close to plain Perl
under the hood.
This post explores the technical aspects of both bless
and Class::Mite
- with benchmarks for Basic
, Extends
, Role
and Attributes
.
A) Basic Class Definition
Using bless:
package BlessPerson;
use strict;
use warnings;
sub new {
my ($class, %args) = @_;
return bless { %args }, $class;
}
sub greet {
my ($self) = @_;
return "Hello ", $self->{name}, "!\n";
}
Using Class:
package ClassPerson;
use Class;
sub greet {
my ($self) = @_;
return "Hello ", $self->{name}, "!\n";
}
Using Moo:
package MooPerson;
use Moo;
has 'name' => (is => 'rw');
sub greet {
my ($self) = @_;
return "Hello ", $self->name, "!\n";
}
Benchmark Script
package main;
use BlessPerson;
use ClassPerson;
use MooPerson;
use Benchmark;
timethese(10_000_000, {
bless => sub { BlessPerson->new(name => 'Joe')->greet; },
class => sub { ClassPerson->new(name => 'Joe')->greet; },
moo => sub { MooPerson->new(name => 'Joe')->greet; },
});
Result
Benchmark: timing 10000000 iterations of bless, class, moo...
bless: 5 wallclock secs ( 4.32 usr + 0.00 sys = 4.32 CPU) @ 2314814.81/s (n=10000000)
class: 6 wallclock secs ( 5.32 usr + 0.00 sys = 5.32 CPU) @ 1879699.25/s (n=10000000)
moo: 7 wallclock secs ( 5.79 usr + 0.00 sys = 5.79 CPU) @ 1727115.72/s (n=10000000)
Comparison
Aspect bless Class
------------------------------------------------------------
Boilerplate Manual constructor Auto-generated new()
Structure Minimal Declarative
Type checks Manual Optional
Speed ~2.31M/s ~1.87M/s
Summary
Framework Ops/sec Relative Difference
bless 2.31M 100% —
Class 1.87M ~81% −19%
Moo 1.72M ~75% −25%
Class
is ~20%
slower than raw bless
and roughly 8–9%
faster than Moo
B) Inheritance (extends)
Using bless:
package BlessParent;
use strict;
use warnings;
sub new {
my ($class, %args) = @_;
return bless { %args }, $class;
}
sub speak {
my ($self) = @_;
return $self->{name}, " bark!\n";
}
package BlessChild;
use strict;
use warnings;
use parent qw/BlessParent/;
Using Class:
package ClassParent;
use Class;
sub speak {
my ($self) = @_;
return $self->{name}, " bark!\n";
}
package ClassChild;
use Class;
extends qw/ClassParent/;
Using Moo:
package MooParent;
use Moo;
has 'name' => (is => 'rw');
sub speak {
my ($self) = @_;
return $self->{name}, " neigh!\n";
}
package MooChild;
use Moo;
extends qw/MooParent/;
Benchmark Script
package main;
use BlessChild;
use ClassChild;
use MooChild;
use Benchmark;
timethese(10_000_000, {
bless => sub { BlessChild->new(name => 'Tom')->speak; },
class => sub { ClassChild->new(name => 'Tom')->speak; },
moo => sub { MooChild->new(name => 'Tom')->speak; },
});
Result
Benchmark: timing 10000000 iterations of bless, class, moo...
bless: 5 wallclock secs ( 4.11 usr + 0.00 sys = 4.11 CPU) @ 2433090.02/s (n=10000000)
class: 6 wallclock secs ( 5.12 usr + 0.00 sys = 5.12 CPU) @ 1953125.00/s (n=10000000)
moo: 5 wallclock secs ( 5.30 usr + 0.00 sys = 5.30 CPU) @ 1886792.45/s (n=10000000)
Comparison
Aspect bless + parent Class + extends
-------------------------------------------------------
Inheritance syntax Procedural Declarative
Method lookup Native Native
Ease of use Simple Cleaner
Overhead None Minimal
Summary
extends
in Class
abstracts use parent
, giving uniform
and modern inheritance
declarations with minimal performance impact.
C) Roles (with)
Roles
allow behaviour composition without classical inheritance. They’re conceptually like mix-ins
but with formal requirements.
Perl
’s core doesn’t have roles
— you must emulate them manually:
Using bless:
package BlessRole;
use strict;
use warnings;
sub speak { ... }
package BlessDog;
use strict;
use warnings;
use parent qw/BlessRole/;
sub new {
my ($class, %args) = @_;
return bless { %args }, $class;
}
sub speak {
my ($self) = @_;
return $self->{name}, " bark!\n";
}
Using Class/Role:
package ClassRole;
use Role;
requires qw/speak/;
package ClassDog;
use Class;
with qw/ClassRole/;
sub speak {
my ($self) = @_;
return $self->{name}, " bark!\n";
}
Using Moo::Role:
package MooRole;
use Moo::Role;
requires qw/speak/;
package MooDog;
use Moo;
has 'name' => (is => 'rw');
with qw/MooRole/;
sub speak {
my ($self) = @_;
return $self->{name}, " neigh!\n";
}
Benchmark Script
package main;
use BlessDog;
use ClassDog;
use MooDog;
use Benchmark;
timethese(10_000_000, {
bless => sub { BlessDog->new(name => 'Tommy')->speak; },
class => sub { ClassDog->new(name => 'Tommy')->speak; },
moo => sub { MooDog->new(name => 'Tommy')->speak; },
});
Result
Benchmark: timing 10000000 iterations of bless, class, moo...
bless: 3 wallclock secs ( 4.15 usr + 0.00 sys = 4.15 CPU) @ 2409638.55/s (n=10000000)
class: 6 wallclock secs ( 5.17 usr + 0.00 sys = 5.17 CPU) @ 1934235.98/s (n=10000000)
moo: 5 wallclock secs ( 4.64 usr + 0.00 sys = 4.64 CPU) @ 2155172.41/s (n=10000000)
Comparison
Aspect bless Role
---------------------------------------------------------
Role concept None (manual) Native
Composition Inheritance only Declarative with
Requirements Manual Enforced via requires
Overhead Minimal Slight
Summary
Roles
are a major advantage of Class::Mite
. They formalize what used to be ad hoc, with little runtime cost.
D) Attributes
This is where Class::Mite
shines. Manual constructors get replaced by declarative has
statements.
Using bless:
package BlessPerson;
use strict;
use warnings;
sub new {
my ($class, %args) = @_;
for (qw/name age/) {
die "Missing required key $_.\n"
unless (exists $args{$_});
}
$args{country} //= 'UK';
return bless { %args }, $class;
}
sub desc {
my ($self) = @_;
return sprintf("%s is %d years old and from %s.\n",
$self->{name},
$self->{age},
$self->{country});
}
Using Class::More:
package ClassPerson;
use Class::More;
has name => (required => 1);
has age => (required => 1);
has country => (default => sub { 'UK' });
sub desc {
my ($self) = @_;
return sprintf("%s is %d years old and from %s.\n",
$self->name,
$self->age,
$self->country);
}
Using Moo:
package MooPerson;
use Moo;
has name => (is => 'rw', required => 1 );
has age => (is => 'rw', required => 1 );
has country => (is => 'rw', default => sub { 'UK'});
sub desc {
my ($self) = @_;
return sprintf("%s is %d years old and from %s.\n",
$self->name,
$self->age,
$self->country);
}
Benchmark Script
package main;
use BlessPerson;
use ClassPerson;
use MooPerson;
use Benchmark;
my $bless_person = BlessPerson->new(name => 'Tom', age => 20);
my $class_person = ClassPerson->new(name => 'Tom', age => 20);
my $moo_person = MooPerson->new(name => 'Tom', age => 20);
timethese(10_000_000, {
bless => sub { $bless_person->desc; },
class => sub { $class_person->desc; },
moo => sub { $moo_person->desc; },
});
Result
Benchmark: timing 10000000 iterations of bless, class, moo...
bless: 2 wallclock secs ( 1.61 usr + 0.00 sys = 1.61 CPU) @ 6211180.12/s (n=10000000)
class: 1 wallclock secs ( 1.63 usr + 0.00 sys = 1.63 CPU) @ 6134969.33/s (n=10000000)
moo: 2 wallclock secs ( 1.62 usr + 0.00 sys = 1.62 CPU) @ 6172839.51/s (n=10000000)
Comparison
Aspect bless Class::More
-----------------------------------------------
Attribute setup Manual Declarative
Defaults Manual Built-in
Required keys Manual Built-in
Readability Procedural Declarative
Performance Equal Equal
Summary
Once the object is built, accessors
are plain Perl
methods — no runtime penalty. This gives you Moo
-like ergonomics with pure-Perl
performance.
Summary of Benchmarks
Bless Class Moo Fastest Class vs Moo Class vs Bless
ops/s ops/s ops/s
--------------------------------------------------------------------------------------------------
A) Basic Class Definition 2.31M 1.87M 1.72M Bless +8.8% faster 19% slower
B) Inheritance (extends) 2.43M 1.95M 1.88M Bless +3.5% faster 19.7% slower
C) Roles (with) 2.40M 1.93M 2.15M Bless 10.2% slower 19.7% slower
D) Attributes (method call) 6.21M 6.13M 6.17M Bless ~equal (±1%) 1.2% slower
Interpretation
Class::Mite
adds only a tiny initialization cost. Runtime method
and attribute access
remain as fast as native Perl
.
You gain cleaner syntax, roles and safer constructors.
Conclusion
Class::Mite
brings modern OOP
syntax to Perl
without abandoning its simplicity.
- Faster and lighter than Moose/Moo
- Cleaner and safer than raw bless
- Fully compatible with existing Perl objects
- Declarative roles, inheritance and attributes
If you love Perl
’s flexibility but want less boilerplate and more expressiveness - Class::Mite
bridges that gap beautifully.
Happy Hacking !!!