Introducing Marlin
Source: Dev.to

Introduction
To be honest, probably not.
But here’s why you might want to give Marlin a try anyway.
- Most of your constructors and accessors will be implemented in XS and be really, really fast.
- If you accept a few basic principles like “attributes should usually be read‑only”, it can be really, really concise to declare a class and its attributes.
An example
use v5.20.0;
use experimental qw(signatures);
# Import useful constants, types, etc.
use Marlin::Util -all, -lexical;
use Types::Common -all, -lexical;
package Person {
use Marlin
'given_name!' => NonEmptyStr,
'family_name!' => NonEmptyStr,
'name_style' => { enum => [qw/western eastern/], default => 'western' },
'full_name' => { is => lazy, builder => true },
'birth_date?';
sub _build_full_name ( $self ) {
return sprintf( '%s %s', uc($self->family_name), $self->given_name )
if $self->name_style eq 'eastern';
return sprintf( '%s %s', $self->given_name, $self->family_name );
}
}
package Payable {
use Marlin::Role
-requires => [ 'bank_details' ];
sub make_payment ( $self ) {
...;
}
}
package Employee {
use Marlin
-extends => [ 'Person' ],
-with => [ 'Payable' ],
'bank_details!' => HashRef,
'employee_id!' => Int,
'manager?' => { isa => 'Employee' };
}
my $manager = Employee->new(
given_name => 'Simon',
family_name => 'Lee',
name_style => 'eastern',
employee_id => 1,
bank_details => {},
);
my $staff = Employee->new(
given_name => 'Lea',
family_name => 'Simons',
employee_id => 2,
bank_details => {},
manager => $manager,
);
printf(
"%s's manager is %s.\n",
$staff->full_name,
$staff->manager->full_name,
) if $staff->has_manager;
Some things you might notice
- It supports most of the features of Moose… or most of the ones you actually use anyway.
- Declaring an attribute is often as simple as listing its name on the
use Marlinline. - It can be followed by options, but if you’re happy with Marlin’s defaults (read‑only attributes), you don’t need them.
- Use
!to quickly mark an attribute as required instead of{ required => true }. - Use
?to request a predicate method instead of{ predicate => true }.
Benchmarks
My initial benchmarking shows that Marlin is fast.
Constructors
Rate Tiny Plain Moo Moose Marlin Core
Tiny 1317/s -- -2% -48% -53% -54% -72%
Plain 1340/s 2% -- -47% -53% -53% -72%
Moo 2527/s 92% 89% -- -11% -12% -47%
Moose 2828/s 115% 111% 12% -- -2% -40%
Marlin 2873/s 118% 114% 14% 2% -- -39%
Core 4727/s 259% 253% 87% 67% 65% --
Only the new Perl core class keyword generates a constructor faster than Marlin’s, and it is significantly faster. However, object construction is only part of what you are likely to need.
Accessors
Rate Tiny Moose Plain Core Moo Marlin
Tiny 17345/s -- -1% -3% -7% -36% -45%
Moose 17602/s 1% -- -2% -6% -35% -44%
Plain 17893/s 3% 2% -- -4% -34% -44%
Core 18732/s 8% 6% 5% -- -31% -41%
Moo 27226/s 57% 55% 52% 45% -- -14%
Marlin 31688/s 83% 80% 77% 69% 16% --
By “accessors” I mean not just standard getters and setters, but also predicate methods and clearers. Marlin and Moo both use Class::XSAccessor when possible, giving them a significant lead over the others. Marlin also creates aliases for parent‑class methods directly in the child’s symbol table, allowing Perl to bypass a lot of the normal method‑resolution overhead.
I expected class to perform better; its readers and writers are currently implemented in pure Perl, though there is room for improvement in future releases.
Native Traits / Handles‑Via / Delegations
Rate Tiny Core Plain Moose Moo Marlin
Tiny 675/s -- -56% -57% -59% -61% -61%
Core 1518/s 125% -- -4% -8% -13% -13%
Plain 1581/s 134% 4% -- -4% -9% -10%
Moose 1642/s 143% 8% 4% -- -5% -6%
Moo 1736/s 157% 14% 10% 6% -- -1%
Marlin 1752/s 160% 15% 11% 7% 1% --
If you don’t know what I mean by native traits, it’s the ability to create small helper methods like this:
sub add_language ( $self, $lang ) {
push $self->languages->@*, $lang;
}
As part of the attribute definition
use Marlin
languages => {
is => 'ro',
isa => ArrayRef[Str],
default => sub { [] },
handles => {
add_language => 'push',
all_languages => 'elements',
},
};
Code Example
=> ArrayRef[Str],
default => [],
handles_via => 'Array',
handles => { add_language => 'push', count_languages => 'count' },
};
There’s not an awful lot of difference between the performance of most of these, but Marlin slightly wins. Marlin and Moose are also the only frameworks that include this out of the box without needing extension modules.
Note: The
default => []is intentional. You can set an empty arrayref or hashref as a default, and Marlin will treat it as if you wrotedefault => sub { [] }. It cleverly skips calling the coderef (which is slow) and instead creates a reference to a new empty array in XS (fast)!
Combined Benchmark
| Rate | Tiny | Plain | Core | Moose | Moo | Marlin | |
|---|---|---|---|---|---|---|---|
| Tiny | 545/s | — | -48% | -56% | -58% | -60% | -64% |
| Plain | 1051/s | 93% | — | -16% | -19% | -22% | -31% |
| Core | 1249/s | 129% | 19% | — | -4% | -8% | -18% |
| Moose | 1304/s | 139% | 24% | 4% | — | -4% | -14% |
| Moo | 1355/s | 148% | 29% | 8% | 4% | — | -11% |
| Marlin | 1519/s | 179% | 45% | 22% | 17% | 12% | — |
A realistic snippet that constructs objects and calls a bunch of accessors and delegations shows Marlin performing very well.
Lexical Accessors and Private Attributes
Marlin has first‑class support for lexical methods!
use v5.42.0;
package Widget {
use Marlin
name => { isa => Str },
internal_id => { reader => 'my internal_id', storage => 'PRIVATE' };
...
printf "%d: %s\n", $w->&internal_id, $w->name;
}
# dies because internal_id is lexically scoped
Widget->new->&internal_id;
- The
->&operator was added in Perl 5.42. On older Perls (5.12+), lexical methods are still supported but must be called with function‑call syntax, e.g.,internal_id($w). storage => "PRIVATE"tells Marlin to use inside‑out storage for that attribute, so accessing it directly via$obj->{internal_id}will not work. This provides true private attributes.- On Perl 5.18 and newer you can also declare lexical methods with the normal
my sub foosyntax, giving you private attributes as well as private methods.
Constant Attributes
package Person {
use Marlin
name => { isa => Str, required => true },
species_name => { isa => Str, constant => "Homo sapiens" };
}
- Constant attributes are declared like regular ones but are always read‑only and cannot be passed to the constructor.
- They support delegations, provided the delegated method does not attempt to change the value.
Perl Version Support
Although some lexical features require newer Perl versions, Marlin runs on Perl 5.8.8 and later.
Future Directions
Some ideas I’ve had:
- If Moose is loaded, create meta‑object‑protocol stuff for Marlin classes and roles, similar to what Moo does.
