The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Object::InsideOut - Comprehensive inside-out object support module

VERSION

This document describes Object::InsideOut version 0.05.00

SYNOPSIS

 package My::Class; {
     use Object::InsideOut;

     # Generic field with combined accessor
     my %data :Field('Accessor' => 'data');

     # No paramters to ->new()
 }

 package My::Class::Sub; {
     use Object::InsideOut qw(My::Class);

     # List field with separate 'get' and 'set' accessors
     my %info :Field('Get' => 'get_info', 'Set' => 'set_info', 'Type' => 'LIST');

     # Takes 'INFO' as an optional parameter to ->new()
     my %init_args :InitArgs = (
         'INFO' => {
             'Field'   => \%info,
             'Default' => 'none',
             'Type'    => 'LIST',
         },
     );
 }

 package main;

 my $obj = My::Class::Sub->new();
 my $info = $obj->get_info();                    # [ 'none' ]
 my $data = $obj->data();                        # undef
 $obj->data(42);
 $data = $obj->data();                           # 42

 $obj = My::Class::Sub->new('info' => 'help');
 $info = $obj->get_info();                       # [ 'help' ]
 $obj->set_info(qw(foo bar baz));
 $info = $obj->get_info();                       # [ 'foo', 'bar', 'baz' ]

DESCRIPTION

This module provides comprehensive support for implementing classes using the inside-out object model.

This module implements inside-out objects as anonymous scalar references that have been blessed into a class with the scalar containing the ID for the object (usually its refaddr). Object data (i.e., fields) are stored in hashes within the class's package and are keyed to the object's ID.

The advantages of the inside-out object model over the blessed hash object model have been extolled elsewhere. See the informational links under "SEE ALSO".

This module offers all the capabilities of Class::Std with the following additional key advantages:

Speed

As fast as blessed hash objects for fetching and setting data, and 2.5-4.5 times faster than Class::Std.

Threads

Class::Std is not thread safe. Object::InsideOUt is thread safe, and thoroughly supports sharing objects between threads using threads::shared.

Flexibility

Allows control over object ID specification, accessor naming, parameter name matching, and more.

mod_perl

Usable from within mod_perl.

Class Declarations

To use this module, your classes will start with:

    use Object::InsideOut;

Sub-classes inherit from base classes with:

    use Object::InsideOut 'My::Parent';

Multiple inheritance is supported:

    use Object::InsideOut qw(My::Parent Another::Parent);

There is no need for use base ..., or to set up @ISA arrays: Object::InsideOut loads the parent module(s), calls their import functions and sets up the sub-class's @ISA array.

If a parent module takes parameters, enclose them in an array ref (mandatory) following the name of the parent class:

    use Object::InsideOut 'My::Parent'      => [ 'param1', 'param2' ],
                          'Another::Parent' => [ 'param' ];

Field Declarations

Object data fields consist of hashes within a class's package into which data are stored using the object's ID as the key. A hash is declared as begin an object field by following its declaration with the :Field attribute:

    my %info :Field;

(The case of the word Field does not matter, but by convention should not be all lowercase.)

Object Creation

Objects are created using the new method which is exported by Object::InsideOut to each class:

    my $obj = My::Class->new();

Classes do not implement their own new method. Class-specific object initialization actions may be handled by :Init methods (see "Object Initialization").

Parameters are passed in as combinations of key => value pairs and/or hash refs:

    my $obj = My::Class->new('param1' => 'value1');
        # or
    my $obj = My::Class->new({'param1' => 'value1'});
        # or even
    my $obj = My::Class->new(
        'param_X' => 'value_X',
        'param_Y' => 'value_Y',
        {
            'param_A' => 'value_A',
            'param_B' => 'value_B',
        },
        {
            'param_Q' => 'value_Q',
        },
    );

Additionally, parameters can be segregated in hash refs for specific classes:

    my $obj = My::Class->new(
        'foo' => 'bar',
        'My::Class'      => { 'param' => 'value' },
        'Parent::Class'  => { 'data'  => 'info'  },
    );

The initialization methods for both classes in the above will get 'foo' => 'bar', My::Class will also get 'param' => 'value', and Parent::Class will also get 'data' => 'info'. In this scheme, class-specific parameters will override general parameters specified at a higher level:

    my $obj = My::Class->new(
        'default' => 'bar',
        'Parent::Class'  => { 'default' => 'baz' },
    );

My::Class will get 'default' => 'bar', and Parent::Class will get 'default' => 'baz'.

Calling new on an object works, too, and operates the same as calling new for the class of the object (i.e., $obj->new() is the same as ref($obj)->new()).

NOTE: You cannot create objects from Object::InsideOut itself:

    # This is an error
    # my $obj = Object::InsideOut->new();

In this way, Object::InsideOut is not an object class, but functions more like a pragma.

Object Cloning

Copies of objects can be created using the clone method which is exported by Object::InsideOut to each class:

    my $obj2 = $obj->clone();

Object Initialization

Object initialization is accomplished through a combination of an :InitArgs labelled hash (explained in detail in the next section), and an :Init labelled subroutine.

The :InitArgs labelled hash specifies the parameters to be extracted from the argument list supplied to the new method. These parameters are then sent to the :Init labelled subroutine for processing:

    package My::Class; {
        my %my_field :Field;

        my %init_args :InitArgs = (
            'MY_PARAM' => qr/MY_PARAM/i,
        );

        sub _init :Init
        {
            my ($self, $args) = @_;

            if (exists($args->{'MY_PARAM'})) {
                $my_field($$self) = $args->{'MY_PARAM'};
            }
        }
    }

    package main;

    my $obj = My::Class->new('my_param' => 'data');

(The case of the words InitArgs and Init does not matter, but by convention should not be all lowercase.)

This :Init labelled subroutine will receive two arguments: The newly created object requiring further initialization (i.e., $self); and a hash ref of supplied arguments that matched :InitArgs specifications. Data processed by the subroutine can be placed into the class's field hashes using the object's ID (i.e., $$self).

Object Initialization Argument Specifications

The parameters to be handled by the new method are specified in a hash that is labelled with the :InitArgs attribute.

The simplest parameter specification is just a tag:

    my %init_args :InitArgs = (
        'DATA' => '',
    );

In this case, if a key => value pair with an exact match of DATA for the key is found in the arguments sent to the new method, then 'DATA' => value will be included in the argument hash ref sent to the :Init labelled subroutine.

Rather than counting on exact matches, regular expressions can be used to specify the parameter:

    my %init_args :InitArgs = (
        'Param' => qr/^PARA?M$/i,
    );

In this case, the argument key could be any of the following: PARAM, PARM, Param, Parm, param, parm, and so on. If a match is found, then 'Param' => value is sent to the :Init subroutine. Note that the :InitArgs hash key is substituted for the original argument key. This eliminates the need for any parameter key pattern matching within the :Init subroutine.

With more complex parameter specifications, the syntax changes. Mandatory parameters are declared as follows:

    my %init_args :InitArgs = (
        # Mandatory parameter requiring exact matching
        'INFO' => {
            'Mandatory' => 1,
        },
        # Mandatory parameter with pattern matching
        'input' => {
            'Regex'     => qr/^in(?:put)?$/i,
            'Mandatory' => 1,
        },
    );

If a mandatory parameter is missing from the argument list to new, an error is generated.

For optional parameters, defaults can be specified:

    my %init_args :InitArgs = (
        'LEVEL' => {
            'Regex'   => qr/^lev(?:el)?|lvl$/i,
            'Default' => 3,
        },
    );

The parameter's type can also be specified:

    my %init_args :InitArgs = (
        'LEVEL' => {
            'Regex'   => qr/^lev(?:el)?|lvl$/i,
            'Default' => 3,
            'Type'    => 'Numeric',
        },
    );

Available types are Numeric (or Num or Number - case-insensitive), List (case-insensitive), a class (e.g., My::Class), or a reference type (e.g., 'HASH', 'ARRAY', etc.). The List type allows a single value (that is then placed in an array ref) or an array ref. For class and ref types, exact case and spelling are required.

You can specify automatic processing for a parameter's value such that it is placed directly info a field hash and not sent to the :Init subroutine:

    my %hosts :Field;

    my %init_args :InitArgs = (
        'HOSTS' => {
            # Allow 'host' or 'hosts' - case-insensitive
            'Regex'     => qr/^hosts?$/i,
            # Mandatory parameter
            'Mandatory' => 1,
            # Allow single value or array ref
            'Type'      => 'List',
            # Automatically put the parameter in %hosts
            'Field'     => \%hosts,
        },
    );

In this case, when a host parameter is found, it is automatically put into the %hosts hash, and a 'HOSTS' => value pair is not sent to the :Init subroutine. In fact, if you specify fields for all your parameters, then you don't even need to have an :Init subroutine! All the work will be taken care of for you.

(In the above, Regex may be Regexp or just Re, and Default may be Defaults or Def. They and the other specifier keys are case-insensitive, as well.)

Automatic Accessor Generation

As part of the "Field Declarations", you can optionally specify the automatic generation of accessor methods. You can specify the generation of a pair of standard-named accessor methods (i.e., prefixed by get_ and set_):

    my %data :Field('Standard' => 'data');

The above results in Object::InsideOut automatically generating accessor methods named get_data and set_data. (The keyword Standard is case-insensitive, and can be abbreviated to Std.)

You can also separately specify the get and/or set accessors:

    my %name :Field('Get' => 'name', 'Set' => 'change_name');
        # or
    my %name :Field('Get' => 'get_name');
        # or
    my %name :Field('Set' => 'new_name');

For the above, you specify the full name of the accessor(s) (i.e., no prefix is added to the given name(s)). (The Get and Set keywords are case-insensitive.)

You can specify the automatic generation of a combined get/set accessor method:

    my %comment :Field('Accessor' => 'comment');

(The keyword Accessor is case-insensitive, and can be abbreviated to Acc or can be specified as get+set or Combined or Combo.) The above generates a method called comment that is equivalent to:

    sub comment
    {
        my ($self, $cmt) = @_;

        if (defined($cmt)) {
            return ($comment{$$self} = $cmt);
        }

        return ($comment{$$self});
    }

For any of the automatically generated methods that perform set operations, the method's return value is the value being set.

Type-checking for the set operation can be specified, as well:

    my %level :Field('Accessor' => 'level', 'Type' => 'Numeric');

Available types are Numeric (or Num or Number - case-insensitive), List or Array (case-insensitive), Hash (case-insensitive), a class (e.g., My::Class), or a reference type (e.g., 'CODE'). For class and ref types, exact case and spelling are required.

The List/Array type permits the accessor to accept multiple value (that are then placed in an array ref) or a single array ref. The Hash type allows multiple key => value pairs (that are then placed in a hash ref) or a single hash ref.

Due to limitations in the Perl parser, you cannot use line wrapping with the :Field attribute:

    # This doesn't work
    # my %level :Field('Get'  => 'level',
    #                  'Set'  => 'set_level',
    #                  'Type' => 'Num');

    # Must be all on one line
    my %level :Field('Get' =>'level', 'Set' => 'set_level', 'Type' => 'Num');

Object ID

By default, the ID of an object is just its refaddr. This should suffice for nearly all cases of class development. If there is a special need for the module code to control the object ID (see Math::Random::MT::Auto as an example), then an :ID labelled subroutine can be specified:

    sub _id :ID
    {
        # Determine a unique object ID
        ...

        return ($id);
    }

For example, a simple sequential numbering scheme (not recommended for real code):

    my $id_seq = 1;

    sub _id :ID
    {
        return ($id++);
    }

Within any class hierachy only one class may specify an :ID subroutine.

Object Replication

Object replication occurs explicitly when the clone method is called on an object, and implicitly when threads are created in a threaded application. In nearly all cases, Object::InsideOut will take care of all the details for you.

In rare cases, a class may require special handling for object replication. It must then provide a subroutine labelled with the :Replicate attribute. This subroutine will be sent two objects: The parent and the clone:

    sub _replicate : Replicate
    {
        my ($parent, $clone) = @_;

        # Special object replication processing
    }

In the case of thread cloning, the $parent object is just an blessed anonymous scalar reference that contains the ID for the object in the parent thread.

The :Replicate subroutine only needs to deal with the special replication processing: Object::InsideOut will handle all the other details.

Object Destruction

Object::InsideOut exports a DESTROY method to each class that deletes an object's data from the object field hashes. If a class requires additional destruction processing (e.g., closing filehandles), then it must provide a subroutine labelled with the :Destroy attribute. This subroutine will be sent the object that is being destroyed:

    sub _destroy : Destroy
    {
        my $obj = $_[0];

        # Special object destruction processing
    }

The :Destroy subroutine only needs to deal with the special destruction processing: The DESTROY method will handle all the other details of object destruction.

Automethods

There are significant issues related to Perl's AUTOLOAD mechanism. Read Damian Conway's description in "AUTOMETHOD()" in Class::Std for more details. Object::InsideOut handles these issues in the same manner.

Classes requiring AUTOLOAD capabilities must provided a subroutine labelled with the :Automethod attribute. The :Automethod subroutine will be called with the object and the arguments in the original method call (the same as for AUTOLOAD). The :Automethod subroutine should return either a subroutine reference that implements the requested method functionality, or else undef to indicate that it doesn't know how to handle the request.

The name of the method being called is passed as $_ instead of $AUTOLOAD, and does not have the class name prepended to it. If the :Automethod subroutine also needs to access the $_ from the caller's scope, it is available as $CALLER::_.

    sub _automethod :Automethod
    {
        my $self = shift;
        my @args = @_;

        my $method_name = $_;

        # If method can be handled by this class
        if (...) {
            my $handler = sub { .... };

            return ($handler);
        }

        # This class cannot handle the method request
        return;
    }

Cumulative Methods

As with Class::Std, Object::InsideOut provides a mechanism for creating methods whose effects accumulate through the class hierarchy. See ":CUMULATIVE()" in Class::Std for details. Such methods are tagged with the :Cumulative attribute (or :Cumulative(top down)), and propogate from the top down through the class hierarchy (i.e., from the base classes down through the child classes). If tagged with :Cumulative(bottom up), they will propogated from the object's class upwards through the parent classes.

Note that this directionality is the reverse of Class::Std which defaults to bottom up, and uses BASE FIRST to mean from the base classes downward through the children. (I eschewed the use of the term BASE FIRST because I felt it was ambiguous: base could refer to the base classes at the top of the hierarchy, or the child classes at the base (i.e., bottom) of the hierarchy.)

Chained Methods

In addition to :Cumulative, Object::InsideOut provides a way of creating methods that are chained together so that their return values are passed as input arguments to other similarly named methods in the same class hierarchy.

For example, imagine you had a method called format_name that formats a name for display:

    package Subscriber; {
        use Object::InsideOut;

        sub format_name {
            my ($self, $name) = @_;

            # Strip leading and trailing whitespace
            $name =~ s/^\s+//;
            $name =~ s/\s+$//;

            return ($name);
        }
    }

And elsewhere you have a second class that formats the case of names:

    package Person; {
        use Lingua::EN::NameCase qw(nc);
        use Object::InsideOut;

        sub format_name {
            my ($self, $name) = @_;

            # Attempt to properly case names
            return (nc($name));
        }
    }

And you decide that you'd like to perform some formatting of your own, and then have all the parent methods apply their own formatting. Normally, if you have a single parent class, you'd just call the method directly with $self-SUPER::format_name($name)>, but if you have more than one parent class you'd have to explicitly call each method directly:

    package Customer; {
        use Object::InsideOut qw(Person Subscriber);

        sub format_name {
            my ($self, $name) = @_;

            # Compress all whitespace into a single space
            $name =~ s/\s+/ /g;

            $name = $self->Subscriber::format_name($name);
            $name = $self->Person::format_name($name);

            return $name;
        }
    }

With Object::InsideOut you'd add the :CHAINED attribute to each class's format_name method, and the methods will be chained together automatically:

    package Subscriber; {
        use Object::InsideOut;

        sub format_name :Chained {
            my ($self, $name) = @_;

            # Strip leading and trailing whitespace
            $name =~ s/^\s+//;
            $name =~ s/\s+$//;

            return ($name);
        }
    }

    package Person; {
        use Lingua::EN::NameCase qw(nc);
        use Object::InsideOut;

        sub format_name :Chained {
            my ($self, $name) = @_;

            # Attempt to properly case names
            return (nc($name));
        }
    }

    package Customer; {
        use Object::InsideOut qw(Person Subscriber);

        sub format_name :Chained {
            my ($self, $name) = @_;

            # Compress all whitespace into a single space
            $name =~ s/\s+/ /g;

            return ($name);
        }
    }

So passing in someone's name to format_name in Customer would cause leading and trailing whitespace to be removed, then the name to be properly cased, and finally whitespace to be compressed to a single space. The resulting $name would be returned to the caller.

If you label the method with the :Chained(bottom up) attribute, then the chained methods are called starting with the object's class and working upwards through the class hierarchy, similar to how :Cumulative(bottom up) works.

Restricted and Private Methods

Access to certain methods can be narrowed by use of the :Restricted and :Private attributes. :Restricted methods can only be called from within the class's hierarchy. :Private methods can only be called from within the method's class.

Without the above attributes, most methods have public access. If desired, you may explicitly label them with the :Public attribute.

Hidden Methods

For subroutines marked with the following attributes:

:ID
:Init
:Replicate
:Destroy
:Automethod

Object::InsideOut normally renders them uncallable (hidden) to class and application code (as they should normally only be needed by Object::InsideOut itself). If needed, this behavior can be overridden by adding the PUBLIC, RESTRICTED or PRIVATE keywords following the attribute:

    sub _init :Init(private)    # Callable from within this class
    {
        my ($self, $args) = @_;

        ...
    }

NOTE: The above cannot be accomplished by using the corresponding attributes. For example:

    # sub _init :Init :Private    # Wrong syntax - doesn't work

Object Coercion

As with Class::Std, Object::InsideOut provides support for various forms of object coercion through the overload mechanism. See ":STRINGIFY" in Class::Std for details. The following attributes are supported:

:Stringify
:Numerify
:Boolify
:Arrayify
:Hashify
:Globify
:Codify

Coercing an object to a scalar (:Scalarify) is not supported as $$obj is the ID of the object and cannot be overridden.

The _DUMP() Method

Object::InsideOut exports a method called _DUMP to each class that returns either a hash or string representation of the object that invokes the method.

The hash representation is returned when _DUMP is called without arguments. The hash ref that is returned has keys for each of the classes that make up the object's hierarchy. The values for those keys are hash refs containing key => value pairs for the object's fields. The name for a field will be either the tag from the :InitArgs array that is associated with the field, its get method name, its set method name, or, failing all that, a string of the form HASH(0x....).

When called with a true argument, _DUMP returns a string version of the hash representation using Data::Dumper.

THREAD SUPPORT

This module fully supports threads (i.e., is thread safe), and for Perl 5.8.0 and beyond also supports the sharing of Object::InsideOut objects between threads using threads::shared. To use Object::InsideOut in a threaded application, you must put use threads; at the beginning of the application. (The use of require threads; after the program is running is not supported.) If object sharing it to be utilized, then use threads::shared; should follow.

For Perl 5.6.0 to 5.7.1, you can use threads;, but you must call Object::InsideOut-CLONE()> as the first line of the subroutine argument to threads-create()>:

    use threads;

    package My::Class; {
        use Object::InsideOut;
        ...
    }

    package main;

    my $obj = My::Class->new();

    my $thr = threads->create(sub {
            Object::InsideOut->CLONE() if ($] < 5.007002);

            ...
            $obj->method();
            ...
        });

For Perl 5.7.2 and 5.7.3, you can use threads; with no restrictions.

For Perl 5.8.0 onwards, if you just use threads;, then objects from one thread will be copied and made available in a child thread.

The addition of <use threads::shared;> in and of itself does not alter the behavior of Object::InsideOut objects. The default behavior is to not share objects between threads (i.e., they act the same as with use threads; alone).

To enable the sharing of objects between threads, you must specify which classes will be involved with thread object sharing. There are two methods for doing this. The first involves setting a ::shared variable for the class prior to its use:

    use threads;
    use threads::shared;

    $My::Class::shared = 1;
    use My::Class;

The other method is for a class to add a :SHARED flag to its use Object::InsideOut ... declaration:

    package My::Class; {
        use Object::InsideOut ':SHARED';
        ...
    }

When either sharing flag is set for one class in an object hierarchy, then all the classes in the hierarchy are affected.

If a class cannot support thread object sharing (e.g., one of the object fields contains code refs [which Perl cannot share between threads]), it should specifically declare this fact:

    package My::Class; {
        use Object::InsideOut ':NOT_SHARED';
        ...
    }

However, you cannot mixed thread object sharing classes with non-sharing classes in the same class hierarchy:

    use threads;
    use threads::shared;

    package My::Class; {
        use Object::InsideOut ':SHARED';
        ...
    }

    package Other::Class; {
        use Object::InsideOut ':NOT_SHARED';
        ...
    }

    package My::Derived; {
        use Object::InsideOut qw(My::Class Other::Class);   # ERROR!
        ...
    }

Here is a complete example with thread object sharing enabled:

    use threads;
    use threads::shared;

    package My::Class; {
        use Object::InsideOut ':SHARED';

        # One list-type field
        my %data : Field('Accessor' => 'data', 'Type' => 'List');
    }

    package main;

    # New object
    my $obj = My::Class->new();

    # Set the object's 'data' field
    $obj->data(qw(foo bar baz));

    # Print out the object's data
    print(join(', ', @{$obj->data()}), "\n");              # "foo, bar, baz"

    # Create a thread and manipulate the object's data
    my $rc = threads->create(
            sub {
                # Read the object's data
                my $data = $obj->data();
                # Print out the object's data
                print(join(', ', @{$data}), "\n");         # "foo, bar, baz"
                # Change the object's data
                $obj->data(@$data[1..2], 'zooks');
                # Print out the object's modified data
                print(join(', ', @{$obj->data()}), "\n");  # "bar, baz, zooks"
                return (1);
            }
        )->join();

    # Show that the changes in the object are visible in the parent thread
    # I.e., this shows that the object was indeed shared between threads
    print(join(', ', @{$obj->data()}), "\n");              # "bar, baz, zooks"

USAGE WITH require and mod_perl

Object::InsideOut performs most of its magic during Perl's CHECK phase. This causes problems when trying to load packages/classes at runtime using require, or when using mod_perl. To overcome this issue, Object::InsideOut's CHECK phase processing has been packaged into a subroutine that can be called by the application in these instances.

For an application that loads packages/classes at runtime, Object::InsideOut::INITIALIZE should be run immediately after the require statement:

    eval {
        require My::Class;
        Object::InsideOut::INITIALIZE;
    };

For CGIs running under mod_perl, Object::InsideOut::INITIALIZE should be run prior to any objects being created - preferrably right after all use statements that load inside-out object classes:

    use My::Class;
    Object::InsideOut::INITIALIZE;

DIAGNOSTICS

This module uses Exception::Class for reporting errors. The base error class for this module is OIO.

    my $obj;
    eval { $obj = My::Class->new(); };
    if (my $e = OIO->caught()) {
        print(STDERR "Failure creating object: $e\n");
        exit(1);
    }
*Invalid HASH attribute

Forgot to 'use Object::InsideOut qw(Parent::Class ...);'

BUGS AND LIMITATIONS

Cannot overload an object to a scalar context (i.e., can't :SCALARIFY).

You cannot use two instances of the same class with mixed thread object sharing in same application.

Cannot use attributes on subroutine stubs (i.e., forward declaration without later definition) with C:<:Automethod>:

    package My::Class; {
        sub method : Private;   # Will not work

        sub _automethod : Automethod
        {
            # Code to handle call to 'method' stub
        }
    }

Due to limitations in the Perl parser, you cannot use line wrapping with the :Field attribute.

If a set accessor's type is SCALAR, then it can store any inside-out object type in it. If it is HASH, then it can store any ordinary object type.

If you save an object inside another object when thread-sharing, you must rebless it when you get it out:

    my $bb = BB->new();
    my $aa = AA->new();
    $aa->save($bb);
    my $cc = $aa->get();
    bless($cc, 'BB');

If your version of Perl does not support the weaken function found in Scalar::Util, and you use threads;, then you need to manually destroy objects using $obj-DESTROY()>. If no weaken and you use threads; and use threads::shared, then you need to manually destroy non-shared objects using $obj-DESTROY()>.

There are numerous bugs related to threads and threads::shared in various versions of Perl on various platforms that cause Object::InsideOut tests to fail:

ActiveState Perl 5.8.4 on Windows - Object methods missing in threads
Perl 5.8.4 through 5.8.6 on Solaris - Perl core dumps when destroying shared objects

The best solution for the above is to upgrade your version of Perl. Barring that, you can tell CPAN to force the installation of Object::InsideOut.

Please submit any bugs, problems, suggestions, patches, etc. to: http://rt.cpan.org/NoAuth/Bugs.html?Dist=Object-InsideOut

REQUIREMENTS

Exception::Class v1.22 or higher

Scalar::Util v1.17 or higher recommended

TO DO

Improve these docs.

Improve test suite.

SEE ALSO

Inside-out Object Model: http://www.perlmonks.org/index.pl?node_id=219378, http://www.perlmonks.org/index.pl?node_id=483162, Chapters 15 and 16 of Perl Best Practices by Damian Conway

ACKNOWLEDGEMENTS

Abigail-II on Perl Monks inside-out objects in general.

Damian Conway for Class::Std.

David A. Golden <david AT dagolden DOT com> for thread handling for inside-out objects.

Dan Kubb <dan.kubb-cpan AT autopilotmarketing DOT com> for :CHAINED methods.

AUTHOR

Jerry D. Hedden, <jdhedden AT 1979 DOT usna DOT com>

COPYRIGHT AND LICENSE

Copyright 2005 Jerry D. Hedden. All rights reserved.

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.