NAME

My::Object - a simple object system for Perl 5

SYNOPSIS

package Local::Pony {
    use My::Object { name => undef };

    sub BUILD_Pony {
        my ($self, $name) = @_;
        $self->name = $name;
    }

    sub eatCarrot {
        printf "%s eats a carrot.\n", shift->name;
    }
}

*pony = Local::Pony->NEW;

my $cuddles = pony("Cuddles");
$cuddles->eatCarrot;

STATUS

Unstable. Stuff might change. As I have no clue what I'm doing, the code might be evil.

DESCRIPTION

A small object system using blessed hashes with autogenerated lvalue accessors. Code reuse is achieved via composition instead of inheritance: @ISA is not used. Instead, all methods are flattened into the package.

This module takes a worse-is-better approach: There's no encapsulation, no inheritance, no type checking or other bells and whistles.

This is not Moose; it is not even Mo.

Class Declaration

A class is just a package that invokes use on another class, in particular My::Object. This composes the callee into the caller, copying all sub declarations into the package.

The use statement takes a hash of members as optional argument, mapping names to initializers. Accessors for these members will be generated automatically.

Any sub defined within the package may be used as a method, with a reference to the object passed as first argument.

Dynamic Initialization

Initializers are treated as constants by default. If you need to initialize a member dynamically, you may provide a sub reference as initializer. You need to prefix the member name with a -. This prefix is not part of the name proper.

use Scalar::Util qw(refaddr);

package Local::Node {
    use My::Object {
        name => undef,
        -children => sub { [] },
    };
}

my $n1 = Local::Node::NEW->();
my $n2 = Local::Node::NEW->();
printf "0x%X != 0x%X\n", refaddr($n1->children), refaddr($n2->children);

Conflict Resolution

You may use multiple use statements to compose several classes into the same package. Conflicts arise if the classes have methods with the same name. In contrast, members of the same name will map to the same slot and no conflict arises.

Conflicts can be resolved by adding the line

use subs qw(name_of_conflicting_method);

before using any of the conflicting classes and manually providing an appropriate method implementation:

package Local::Alice {
    use My::Object;
    sub transmogrify { print "I'm a tiger!\n" }
}

package Local::Bob {
    use My::Object;
    sub transmogrify { print "I'm a frog!\n" }
}

package Local::AliceAndBob {
    use Local::Alice;
    use Local::Bob;
}

This will die with Subroutine transmogrify redefined, so we need to resolve the conflict:

package Local::AliceAndBob {
    use subs qw(transmogrify);
    use Local::Alice;
    use Local::Bob;

    sub transmogrify {
        print "Alice: ";
        Local::Alice::transmogrify(@_);
        print "Bob: ";
        Local::Bob::transmogrify(@_);
    }
}

Local::AliceAndBob::NEW->()->transmogrify;

Object Construction

Any class automatically gets a sub NEW that returns a reference to the default constructor, initializing member variables from named arguments:

package Local::Point {
    use My::Object { x => 0, y => 0 };
}

my $p = Local::Point::NEW->(x => 0.1, y => 0.2);
printf "x=%f, y=%f\n", $p->x, $p->y;

Use the symbol table to manually import the constructor into the current package:

*point = Local::Point::NEW;
my $q = point(x => 0.5, y => 0.2);

It's best to do so at BEGIN time.

Custom Constructors

If you want to provide a custom constructor, prefix the sub's name with BUILD_ and pass the name without prefix to NEW :

package Local::Point {
    use My::Object { x => 0, y => 0 };

    sub BUILD_from_coords {
        my ($self, $x, $y) = @_;
        $self->x = $x;
        $self->y = $y;
    }
}

BEGIN { *point = Local::Point::NEW('from_coords') }

If you name constructor and package the same (only the part after the last :: is relevant), ->NEW will do the right thing:

package Local::Point {
    use My::Object { x => 0, y => 0 }

    sub BUILD_Point { ... }
}

BEGIN { *point = Local::Point->NEW }

BUGS AND DEVELOPMENT

Development happens at Bitbucket. If you found a bug or have a feature request, use the issue tracker over there.

COPYRIGHT AND LICENSE

Copyright (C) 2014 by Christoph Gärtner <cygx@cpan.org>

Distributed under the Boost Software License, Version 1.0