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::clone so 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. The default key 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:

  • Any

  • Defined

  • Str

  • Int

  • Num

  • Bool

  • ArrayRef

  • HashRef

  • CodeRef

  • Object

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:

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)