NAME
Object::Proto::Sugar - Moo-se-like syntax for Object::Proto
VERSION
Version 0.05
SYNOPSIS
package Animal;
use Object::Proto::Sugar -types, -constants;
has name => (is_rw, req, isa => Str);
has sound => (is_rw, isa => Str, default => 'silence');
sub speak { $_[0]->sound }
package Dog;
use Object::Proto::Sugar qw(Str is_rw);
extends 'Animal';
has breed => (is_rw, isa => Str);
package main;
my $dog = new Dog name => 'Rex', sound => 'woof', breed => 'Lab';
print $dog->speak; # woof
print $dog->isa('Animal'); # 1
DESCRIPTION
Object::Proto::Sugar provides Moo-se-like declarative syntax over Object::Proto, giving you has, extends, with, requires, and method modifiers - all compiled down to the zero-overhead XS layer underneath.
Nothing beyond the keywords are imported by default. Type constants (Str, Int, ...) and sugar constants (is_rw, req, ...) are opt-in via import flags or explicit named imports. See "IMPORTING".
KEYWORDS
has $name => (%options)
Declares an attribute. All options are optional unless noted.
has age => (
is => 'rw', # 'rw' (read-write) or 'ro' (read-only)
isa => Int, # type constraint (string or constant)
required => 1, # must be supplied to constructor
default => 0, # default value when parsed a codeblock it's actually installed as a builder
lazy => 1, # compute default on first access
builder => '_build_age',# method name (or 1 for _build_$name)
coerce => sub { ... }, # coerce incoming value
trigger => sub { ... }, # called after value is set
predicate => 1, # install has_$name (or custom name)
clearer => 1, # install clear_$name (or custom name)
reader => 1, # install get_$name (or custom name)
writer => 1, # install set_$name (or custom name)
init_arg => '_age', # constructor key (alias: arg)
weak_ref => 1, # store a weak reference (alias: weak)
);
is
is => 'rw' # read-write accessor
is => 'ro' # read-only (setter dies)
isa
Accepts a type name string, an imported type constant, or a custom coderef:
isa => 'Str' # type name string - always works
isa => Str # type constant - requires: use Object::Proto::Sugar qw(Str)
isa => sub { $_[0] > 0 } # custom check - dies on failure
default
default => 42 # scalar
default => 'text' # string
default => [] # fresh arrayref per object
default => {} # fresh hashref per object
default => sub { ... } # coderef - called as builder
builder
builder => 1 # calls _build_$name on the object
builder => '_my_builder' # calls named method on the object
builder => sub { ... } # anonymous sub installed as builder
predicate and clearer
predicate => 1 # installs has_$name
predicate => 'is_set' # installs is_set
clearer => 1 # installs clear_$name
clearer => 'reset_age' # installs reset_age
reader and writer
reader => 1 # installs get_$name
reader => 'fetch_age' # installs fetch_age
writer => 1 # installs set_$name
writer => 'store_age' # installs store_age
accessor
Install a function-style accessor for maximum performance. The function can be called as fname($obj) (get) or fname($obj, $value) (set), avoiding method dispatch overhead entirely.
accessor => 1 # installs function named after the attribute
accessor => 'fname' # installs function with custom name
Unlike reader/writer, accessor installs a single combined get/set function. reader and writer also install function-style versions alongside their method-style ones when specified.
has age => ( is => 'rw', accessor => 1 );
# method style: $obj->age
# function style: age($obj) or age($obj, 42)
has age => ( is => 'rw', reader => 1, writer => 1 );
# method style: $obj->get_age, $obj->set_age
# function style: get_age($obj), set_age($obj, 42)
init_arg / arg
Override the constructor key used to populate this attribute:
has name => ( is => 'rw', init_arg => '_name' );
# new MyClass _name => 'Alice'
arg is an alias for init_arg.
weak_ref / weak
Store a weak reference. The attribute becomes undef when the referent is garbage-collected:
has parent => ( is => 'rw', weak_ref => 1 );
weak is an alias for weak_ref.
attributes $name => \@spec, ...
A positional shorthand for declaring one or more attributes in a compact array-based syntax, rather than the named-pair style of has.
Each declaration is a name (or arrayref of names) paired with an arrayref specifying [mode, default, \%options]:
attributes x => ['rw'];
attributes y => ['ro', 0];
attributes z => ['rw', sub { [] }, { required => 1 }];
Multiple names sharing the same spec can be declared in one call:
attributes [qw(width height)] => ['rw', 0];
Spec arrayref format
[ $mode ]
[ $mode, $default ]
[ $mode, $default, \%options ]
[ $mode, \%options ] # default supplied inside %options
[ \%options ] # mode defaults to 'ro'
mode -
'ro','rw', or'set'. Defaults to'ro'if omitted.default - a scalar, reference, or coderef. Non-code values are deep-cloned per object via
Object::Proto::cloneso each instance gets its own independent copy. A coderef is installed as a builder.%options - any option accepted by
has:required,lazy,isa,coerce,builder,trigger,predicate,clearer,reader,writer,init_arg/arg,weak_ref/weak,accessor. Thedefaultkey may also be placed here instead of in the positional slot.
Examples:
# equivalent to: has x => (is => 'rw', default => 0)
attributes x => ['rw', 0];
# equivalent to: has tags => (is => 'ro', default => sub { [] })
attributes tags => ['ro', []];
# default inside options hash - identical result
attributes tags => ['ro', { default => [] }];
# multiple names, shared spec
attributes [qw(created_at updated_at)] => ['ro', { required => 1 }];
# full options
attributes email => ['rw', undef, { isa => Str, required => 1 }];
extends @parents
Inherit from one or more Object::Proto classes. Parent slots are copied into the child at define time; @ISA is set up for method dispatch.
extends 'Animal';
extends 'Animal', 'Flyable'; # multiple inheritance
The parent class must have been defined with Object::Proto::Sugar (or Object::Proto::define) before the child's package is compiled.
with @roles
Compose one or more roles into the current class (or role):
with 'Printable';
with 'Printable', 'Serializable';
The role must have been defined before the consuming class.
requires @methods
Declare methods that a consuming class must implement. Only meaningful inside a role (use Object::Proto::Sugar -role):
requires 'name';
requires 'to_string', 'to_hash';
before $method => sub { ... }
Run code before the named method. Receives the same arguments as the method.
before 'save' => sub {
my ($self) = @_;
$self->updated_at(time);
};
after $method => sub { ... }
Run code after the named method. Receives the same arguments as the method.
after 'save' => sub {
my ($self) = @_;
log_action('saved ' . $self->name);
};
around $method => sub { ... }
Wrap a method. The wrapper receives $orig as its first argument:
around 'greet' => sub {
my ($orig, $self, @args) = @_;
return uc $self->$orig(@args);
};
Multiple modifiers can be stacked. before modifiers run in reverse declaration order; after modifiers run in declaration order.
The $method name may be unqualified (resolved to the current package) or fully qualified ('Other::Package::method').
accessor_alias $prefix
Set a package-level prefix for all function-style accessors installed via accessor => 1, reader => 1, and writer => 1. The prefix is prepended with an underscore separator. Custom accessor/reader/writer names (i.e. not 1) are not affected.
package Point;
use Object::Proto::Sugar;
accessor_alias 'pt';
has x => ( is => 'rw', accessor => 1 );
has y => ( is => 'rw', accessor => 1 );
# Installs: pt_x(), pt_y() instead of x(), y()
# Method accessors $obj->x, $obj->y still work
When used with reader and writer:
accessor_alias 'db';
has name => ( is => 'rw', accessor => 1, reader => 1, writer => 1 );
# accessor: db_name()
# reader: db_get_name()
# writer: db_set_name()
Each class in an inheritance chain can have its own alias. When import_accessors is called, each class's accessors use that class's alias:
package Vehicle;
use Object::Proto::Sugar;
accessor_alias 'v';
has speed => ( is => 'rw', accessor => 1 );
package Car;
use Object::Proto::Sugar;
extends 'Vehicle';
accessor_alias 'c';
has brand => ( is => 'rw', accessor => 1 );
package main;
Car->import_accessors;
# Imports: c_brand() from Car, v_speed() from Vehicle
Without accessor_alias, accessor => 1 installs a function named after the attribute as before.
ROLES
Define a role by passing -role to the import:
package Printable;
use Object::Proto::Sugar -role;
requires 'name';
has format => ( is => 'rw', default => 'text' );
sub print_self { $_[0]->name . ' (' . $_[0]->format . ')' }
Consume it with with:
package Document;
use Object::Proto::Sugar qw(Str);
with 'Printable';
has name => ( is => 'rw', isa => Str );
Check consumption at runtime:
Object::Proto::does($doc, 'Printable'); # true
IMPORTING
By default use Object::Proto::Sugar installs only the compile-time keywords (has, attributes, extends, with, requires, before, after, around). Everything else is opt-in:
-role-
Mark the current package as a role instead of a class.
-types-
Import all built-in type constants (
Str,Int,ArrayRef, ...). -constants-
Import all sugar shorthand constants (
is_rw,req,lzy_array, ...). - Named imports
-
Import individual types or constants by name. Uppercase names are treated as types, lowercase as sugar constants. Unknown names croak at compile time.
Examples:
use Object::Proto::Sugar; # keywords only
use Object::Proto::Sugar -types; # + all type constants
use Object::Proto::Sugar -constants; # + all sugar constants
use Object::Proto::Sugar -types, -constants; # + both
use Object::Proto::Sugar qw(Str Int); # + specific types
use Object::Proto::Sugar qw(is_rw req); # + specific sugar constants
use Object::Proto::Sugar qw(Str is_rw req); # + mix of both
use Object::Proto::Sugar -role, -types; # role + all types
TYPE CONSTANTS
Type constants are opt-in. See "IMPORTING" for the full import syntax.
The built-in types are:
AnyDefinedStrIntNumBoolArrayRefHashRefCodeRefObject
Custom types registered with Object::Proto::register_type before use Object::Proto::Sugar is called will also get a constant exported.
SUGAR CONSTANTS
Sugar constants are opt-in shorthand that expand inline into has option lists. See "IMPORTING" for the full import syntax.
use Object::Proto::Sugar -constants;
has name => (is_rw, req);
has tags => (is_ro, lzy_array);
has score => (is_rw, nan); # explicit undef default
Mode constants
ro # 'ro'
rw # 'rw'
is_ro # (is => 'ro')
is_rw # (is => 'rw')
Value constants
nan # undef (explicit undef default)
Option constants
req # (required => 1)
lzy # (lazy => 1)
bld # (builder => 1)
lzy_bld # (lazy_build => 1)
trg # (trigger => 1)
clr # (clearer => 1)
coe # (coerce => 1)
Lazy + default constants
lzy_hash # (lazy => 1, isa => 'HashRef', default => {})
lzy_array # (lazy => 1, isa => 'ArrayRef', default => [])
lzy_str # (lazy => 1, isa => 'Str', default => "")
Default-only constants
dhash # (isa => 'HashRef', default => {})
darray # (isa => 'ArrayRef', default => [])
dstr # (isa => 'Str', default => "")
The default values in these constants are plain refs/scalars. Sugar deep-clones them via Object::Proto::clone so each object instance gets its own independent copy.
Usage examples
has config => (is_rw, lzy_hash);
has items => (is_ro, darray);
has label => (is_rw, dstr);
has name => (is_rw, req, isa => 'Str');
has handler => (is_ro, lzy, bld, isa => 'CodeRef');
# positional attributes syntax also works
attributes score => [rw, 0, { req }];
INHERITANCE
Single parent
package Dog;
use Object::Proto::Sugar qw(Str);
extends 'Animal';
has breed => ( is => 'rw', isa => Str );
Dog inherits all of Animal's slots and methods. Inherited slots occupy the same positions as in the parent so there is no runtime overhead for access.
Multiple parents
extends 'Animal', 'Flyable';
Parent slots are merged left-to-right. A child slot with the same name as a parent slot overrides it.
BENCHMARK
Test: new + set + get
------------------------------------------------------------
Benchmark: running Moo, Mouse, Sugar, Sugar (fn) for at least 3 CPU seconds...
Moo: 3 wallclock secs ( 3.09 usr + 0.00 sys = 3.09 CPU) @ 1264320.39/s (n=3906750)
Mouse: 4 wallclock secs ( 3.15 usr + 0.00 sys = 3.15 CPU) @ 1290237.46/s (n=4064248)
Sugar: 4 wallclock secs ( 3.01 usr + 0.00 sys = 3.01 CPU) @ 2784378.41/s (n=8380979)
Sugar (fn): 4 wallclock secs ( 3.11 usr + 0.00 sys = 3.11 CPU) @ 3174092.93/s (n=9871429)
Rate Moo Mouse Sugar Sugar (fn)
Moo 1264320/s -- -2% -55% -60%
Mouse 1290237/s 2% -- -54% -59%
Sugar 2784378/s 120% 116% -- -12%
Sugar (fn) 3174093/s 151% 146% 14% --
AUTHOR
LNATION <email@lnation.org>
BUGS
Please report any bugs or feature requests to bug-object-proto-sugar at rt.cpan.org, or through the web interface at https://rt.cpan.org/NoAuth/ReportBug.html?Queue=Object-Proto-Sugar. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
SUPPORT
You can find documentation for this module with the perldoc command.
perldoc Object::Proto::Sugar
You can also look for information at:
RT: CPAN's request tracker (report bugs here)
https://rt.cpan.org/NoAuth/Bugs.html?Dist=Object-Proto-Sugar
Search CPAN
LICENSE AND COPYRIGHT
This software is Copyright (c) 2026 by LNATION <email@lnation.org>.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)