Modules · Data · 1.00

Object::Proto

Objects stored as arrays, with slots fixed at compile time and prototype chains.

An alternative to a blessed hash. A class names its slots once, at compile time. Each object is an array, and each accessor a custom op that reads its slot by position. Objects are blessed into their class, so isa, can and your own methods work as they always have.

use Object::Proto;

BEGIN {
    Object::Proto::role('Audited', 'changes:Int:default(0)');

    object 'Account',
        'owner:Str:required:readonly',
        'balance:Num:default(0)';
    Object::Proto::with('Account', 'Audited');
}

sub Account::deposit { my ($self, $n) = @_; $self->balance($self->balance + $n) }

# a modifier: every deposit is counted
Object::Proto::after('Account::deposit', sub { $_[0]->changes($_[0]->changes + 1) });

my $acct = new Account owner => 'ada', balance => 10;
$acct->deposit(5) for 1 .. 3;

print $acct->balance, ' after ', $acct->changes, " changes\n";   # 25 after 3 changes
print Object::Proto::does($acct, 'Audited') ? "audited\n" : '';

eval { $acct->balance('lots') };
print $@;   # Type constraint failed for 'balance': expected Num

What you get

  • Slots fixed at compile time

    A class names its slots once and each object is an array. An accessor is a custom op that reads its slot by position, and the function form, age($obj), is faster again.

  • Types and rules on the slot

    'balance:Num:default(0)': a type checked inline, and required, readonly, lazy with a builder, trigger, weak, private, a predicate and a clearer.

  • Prototype chains

    A slot an object has not set is read from its prototype, and from that one's, as far up as the chain goes. Setting it on the object leaves the prototype untouched.

  • Roles and modifiers

    Roles bring slots and methods and can require methods of the class, and before, after and around wrap a method. Neither costs anything in a class that does not use it.

  • BUILD, DEMOLISH and singletons

    BUILD runs once every argument is in place. DEMOLISH gets a DESTROY only when it is defined. singleton gives a class one lazily made instance.

  • Lock and freeze

    Object::Proto::lock stops new properties until it is undone, and freeze stops every change for good. clone makes an unfrozen shallow copy.

In use

use Object::Proto;

BEGIN { object 'Cat', 'name:Str:required', 'sound:Str', 'lives:Int:default(9)' }

# a prototype: a slot the object has not set is read from it
my $cat = new Cat name => 'Cat', sound => 'meow';
my $tom = new Cat name => 'Tom';
$tom->set_prototype($cat);

print $tom->sound, "\n";                                        # meow
print Object::Proto::has_own_property($tom, 'sound') ? '' : "from its prototype\n";

$tom->sound('purr');                  # its own now; the prototype is untouched
print $tom->sound, ' / ', $cat->sound, "\n";                   # purr / meow

Object::Proto::freeze($tom);
eval { $tom->lives(8) };
print $@;   # Cannot modify frozen object

Measured in its POD

One run, as the POD records it, with no machine named. Making an object, setting a slot and reading it: Object::Proto 3.5 million a second with methods and 5.4 million with the function form, a hand-written hash class 1.8 million, an array class 2.1 million. A bare hash with no class at all does 6.0 million, faster than any of them.

Setting and reading an object already made: the function form 32.7 million a second, methods 13.9 million, the hash class 7.0 million, a bare hash 26.0 million.

For C

object_types.h, found through ExtUtils::Depends: another XS module registers a type from its BOOT section with a C check function, which its POD puts at about 5 cycles a check against about 100 for a Perl callback.

Get it

cpanm Object::Proto