Object...sideOut(3pm)
๐ Object::InsideOut โ the manual, but make it make sense ๐
okay bestie, welcome to Object...sideOut(3pm) โ yes that's the manpage name, they really did that to us, and yes this is the Perl doc for Object::InsideOut, the comprehensive inside-out object support module. Strap in, it's long, it's iconic, we're doing all of it. ๐
โจ NAME โจ
Object::InsideOut โ Comprehensive inside-out object support module
๐ธ VERSION ๐ธ
This document describes Object::InsideOut version 4.05
๐ฆ SYNOPSIS ๐ฆ
ok so here's the whole vibe in one go, no edits, no notes:
package My::Class; {
use Object::InsideOut;
# Numeric field
# With combined get+set accessor
my @data
:Field
:Type(numeric)
:Accessor(data);
# Takes 'INPUT' (or 'input', etc.) as a mandatory parameter to ->new()
my %init_args :InitArgs = (
'INPUT' => {
'Regex' => qr/^input$/i,
'Mandatory' => 1,
'Type' => 'numeric',
},
);
# Handle class-specific args as part of ->new()
sub init :Init
{
my ($self, $args) = @_;
# Put 'input' parameter into 'data' field
$self->set(\@data, $args->{'INPUT'});
}
}
package My::Class::Sub; {
use Object::InsideOut qw(My::Class);
# List field
# With standard 'get_X' and 'set_X' accessors
# Takes 'INFO' as an optional list parameter to ->new()
# Value automatically added to @info array
# Defaults to [ 'empty' ]
my @info
:Field
:Type(list)
:Standard(info)
:Arg('Name' => 'INFO', 'Default' => 'empty');
}
package Foo; {
use Object::InsideOut;
# Field containing My::Class objects
# With combined accessor
# Plus automatic parameter processing on object creation
my @foo
:Field
:Type(My::Class)
:All(foo);
}
package main;
my $obj = My::Class::Sub->new('Input' => 69);
my $info = $obj->get_info(); # [ 'empty' ]
my $data = $obj->data(); # 69
$obj->data(42);
$data = $obj->data(); # 42
$obj = My::Class::Sub->new('INFO' => 'help', 'INPUT' => 86);
$data = $obj->data(); # 86
$info = $obj->get_info(); # [ 'help' ]
$obj->set_info(qw(foo bar baz));
$info = $obj->get_info(); # [ 'foo', 'bar', 'baz' ]
my $foo_obj = Foo->new('foo' => $obj);
$foo_obj->foo()->data(); # 86
likeโฆ look at her. she's doing so much. ๐
๐ DESCRIPTION ๐
so this module? it gives you comprehensive support for building classes with the inside-out object model. that's the whole point, that's the assignment, we ate.
Object::InsideOut implements inside-out objects as anonymous scalar references that get blessed into a class, and that scalar holds the object's ID (usually just a sequence number). for Perl 5.8.3 and later, that scalar reference is set as read-only so nobody can accidentally mess with the ID. and where does the actual object data (the fields) live? inside the class's package, bestie โ either in arrays indexed by the object's ID, or in hashes keyed to the object's ID. ๐ซถ
the virtues of the inside-out object model vs. the blessed hash object model have been hyped up in detail elsewhere โ peep the informational links under SEE ALSO. but briefly, inside-out objects give you these advantages over blessed hash objects:
- Encapsulation โ object data is enclosed inside the class's code and is only reachable through the class-defined interface.
- Field Name Collision Avoidance โ inheritance with blessed hash classes can cause conflicts if any classes use the same name for a field (i.e., hash key). inside-out objects are literally immune to this because object data lives inside each class's package, not in the object itself. slay.
- Compile-time Name Checking โ a super common error with blessed hash classes is misspelling field names:
$obj->{'coment'} = 'Say what?'; # Should be 'comment' not 'coment'
since there's no compile-time checking on hash keys, those errors usually don't show up until runtime. with inside-out objects, text hash keys aren't used for accessing field data at all. field names and the data index (i.e., $$self) get checked by the Perl compiler, so any typos get caught easy peasy with perl -c:
$coment[$$self] = $value; # Causes a compile-time error
# or with hash-based fields
$comment{$$self} = $value; # Also causes a compile-time error
and Object::InsideOut brings all the capabilities of other inside-out object modules plus these extra key advantages:
- Speed โ when you use arrays to store object data, Object::InsideOut objects are as much as 40% faster than blessed hash objects for fetching and setting data, and even with hashes they're still several percent faster than blessed hash objects. no cap. ๐ฅ
- Threads โ Object::InsideOut is thread safe and thoroughly supports sharing objects between threads using
threads::shared. - Flexibility โ lets you control object ID specification, accessor naming, parameter name matching, and much more.
- Runtime Support โ supports classes that may be loaded at runtime (i.e., using
eval { require ...; };). this makes it usable from withinmod_perltoo. also supports additions to class hierarchies, and dynamic creation of object fields during runtime. - Exception Objects โ Object::InsideOut uses
Exception::Classfor handling errors in an OO-compatible manner. - Object Serialization โ Object::InsideOut has built-in support for object dumping and reloading, which you can do either automatically or through class-supplied subroutines. serialization using
Storableis also supported. - Foreign Class Inheritance โ Object::InsideOut lets classes inherit from foreign (i.e., non-Object::InsideOut) classes, so you can sub-class other Perl classes and access their methods from your own objects.
- Introspection โ obtain constructor parameters and method metadata for Object::InsideOut classes. we love a self-aware queen.
๐ง CLASSES ๐ง
to use this module, every one of your classes starts with use Object::InsideOut;:
package My::Class; {
use Object::InsideOut;
...
}
sub-classes (child classes) inherit from base classes (parent classes) by telling Object::InsideOut who the parent is:
package My::Sub; {
use Object::InsideOut qw(My::Parent);
...
}
multiple inheritance? also supported, obviously:
package My::Project; {
use Object::InsideOut qw(My::Class Another::Class);
...
}
Object::InsideOut acts as a replacement for the base pragma: it loads the parent module(s), calls their ->import() methods, and sets up the sub-class's @ISA array. so do not use base ... yourself, and don't try to set up @ISA arrays either. also โ and this one's important โ do not use a class's @ISA array to figure out a class's hierarchy: check INTROSPECTION for how to actually do that. ๐ซ
if a parent class takes parameters (like symbols to be exported via Exporter), wrap them in an array ref (mandatory, bestie) right after the name of the parent class:
package My::Project; {
use Object::InsideOut 'My::Class' => [ 'param1', 'param2' ],
'Another::Class' => [ 'param' ];
...
}
๐ OBJECTS ๐
๐ซ Object Creation ๐ซ
objects are created with the ->new() method, which Object::InsideOut exports to each class, and you invoke it like this:
my $obj = My::Class->new();
then Object::InsideOut handles all the messy details of initializing the object across each class in the invoking class's hierarchy. so classes do not (normally) implement their own ->new() method. let the module cook. ๐ฉ๐ณ
usually, object fields get initially populated with data during object creation by passing parameters to ->new(). parameters come 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',
},
);
plus, parameters can be segregated into 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 above get 'foo' => 'bar', My::Class also gets 'param' => 'value', and Parent::Class also gets 'data' => 'info'. and in this scheme, class-specific parameters override general parameters specified at a higher level:
my $obj = My::Class->new(
'default' => 'bar',
'Parent::Class' => { 'default' => 'baz' },
);
so My::Class gets 'default' => 'bar', and Parent::Class gets 'default' => 'baz'. hierarchy said what it said. ๐
calling ->new() on an object works too, and does the same thing as calling ->new() for the class of that object (i.e., $obj->new() is the same as ref($obj)->new()).
how the parameters passed to ->new() get used to initialize the object is discussed later, under OBJECT INITIALIZATION.
โ ๏ธ NOTE: you cannot create objects from Object::InsideOut itself: perl # This is an error # my $obj = Object::InsideOut->new(); in this way, Object::InsideOut is not an object class โ it functions more like a pragma.
๐ฎ Object IDs ๐ฎ
like we said earlier, this module implements inside-out objects as anonymous, read-only scalar references that get blessed into a class, with the scalar containing the ID for the object.
inside methods, the object gets passed in as the first argument:
sub my_method
{
my $self = shift;
...
}
the object's ID is then obtained by dereferencing the object: $$self. normally you only need this when you're accessing the object's field data:
my @my_field :Field;
sub my_method
{
my $self = shift;
...
my $data = $my_field[$$self];
...
}
at every other time โ and especially in application code โ the object should be treated as an opaque entity. like, don't peek under her hood, she's not your business. ๐
โจ ATTRIBUTES โจ
okay bestie, this is literally where the magic lives. Most of what makes Object::InsideOut so powerful comes from attributes: they're tags you slap on your variables and subroutines, and the attributes module hands them over to Object::InsideOut at compile time. Then Object::InsideOut reads those tags and uses that info to do stuff like build your objects, auto-generate accessors, and so on. ๐
heads up: using attributes is not the same thing as source filtering. Different vibe entirely, don't mix them up ๐ซถ
An attribute is an identifier with a colon in front of it, and optionally some parameters in parentheses after it. Like, check out these attributes on an array โ they declare it as an object field AND ask for an accessor method to be generated for that field:
my @level :Field :Accessor(level);
When you've got multiple attributes on one thing, they can all chill on the same line (like above), or they can each get their own line:
my @level
:Field
:Accessor(level);
BUT. Due to limitations in the Perl parser, the entirety of any one attribute must be on a single line:
# This doesn't work
# my @level
# :Field
# :Accessor('Name' => 'level',
# 'Return' => 'Old');
# Each attribute must be all on one line
my @level
:Field
:Accessor('Name' => 'level', 'Return' => 'Old');
For Object::InsideOut's purposes, the case of an attribute's name does not matter:
my @data :Field;
# or
my @data :FIELD;
That said, by convention (as the attributes module says), an attribute's name shouldn't be all lowercase. so like, <cyan>:field</cyan> is a no from her ๐ซ
๐ธ FIELDS ๐ธ
Field Declarations
Object data fields are basically arrays living inside a class's package, and the data gets stored in them using the object's ID as the array index. To declare an array as an object field, you just follow its declaration with the :Field attribute:
my @info :Field;
Object data fields can also be hashes:
my %data :Field;
BUT โ and this is important โ array access is as much as 40% faster than hash access, so bestie, stick to arrays. If you're wondering when hashes might actually be required, go peep "HASH ONLY CLASSES" ๐
Getting Data
In class code, you can grab data straight out of an object's field array (or hash) using the object's ID:
$data = $field[$$self];
# or
$data = $field{$$self};
Setting Data
Same energy as above โ you can put data directly into an object's field array (or hash) using the object's ID:
$field[$$self] = $data;
# or
$field{$$self} = $data;
BUT โ ๏ธ in threaded applications that use data sharing (i.e., that use threads::shared), the above will not work when the object is shared between threads and the data being stored is an array, hash, or scalar reference (and yes, that includes other objects). Why? Because $data has to be converted into shared data first before it can go into the field. no cap.
So, Object::InsideOut automatically exports a method called ->set() to each class. Use this method in class code to put data into object fields whenever there's a chance the class code might run in an application that uses threads::shared โ i.e., use it to make your class code thread-safe. The ->set() method handles every last detail of converting the data into shared form and storing it in the field. iconic behavior tbh ๐
The ->set() method needs two arguments: a reference to the object field array/hash, and the data (as a scalar) to be put in it:
my @my_field :Field;
sub store_data
{
my ($self, $data) = @_;
...
$self->set(\@my_field, $data);
}
To be super clear: ->set() is used inside class code, not application code. Use it inside any object methods that set data in object field arrays/hashes. ๐ฏ
And if you ever hit a method naming conflict, you can call ->set() using its fully-qualified name:
$self->Object::InsideOut::set(\@field, $data);
๐ฆ OBJECT INITIALIZATION ๐ฆ
Like it says in "Object Creation", object fields get initially populated with data as part of the object creation process โ you do this by passing key => value parameters to the ->new() method. These parameters can either be processed automatically into object fields, or passed along to a class-specific object initialization subroutine. pick your fighter ๐
Field-Specific Parameters
When an object creation parameter lines up directly with an object field, you can tell Object::InsideOut to automatically drop the parameter into the field by adding the :Arg attribute to the field declaration:
my @foo :Field :Arg(foo);
With that in place, the following would result in $val being placed in My::Class's @foo field during object creation:
my $obj = My::Class->new('foo' => $val);
Object Initialization Subroutines
A lot of the time, object initialization parameters don't correspond directly to object fields, or they need special handling. For those cases, parameter processing happens through a combo of an :InitArgs labeled hash and an :Init labeled subroutine. ๐ซถ
The :InitArgs labeled hash specifies which parameters to extract from the argument list you handed to ->new(). Those parameters (and only those parameters) that match the keys in the :InitArgs hash get packaged together into a single hash ref. Then the newly created object plus that parameter hash ref get sent to the :Init subroutine for processing.
Here's an example of a class with one automatically handled field and one :Init handled field:
package My::Class; {
use Object::InsideOut;
# Automatically handled field
my @my_data :Field :Acc(data) :Arg(MY_DATA);
# ':Init' handled field
my @my_field :Field;
my %init_args :InitArgs = (
'MY_PARAM' => '',
);
sub _init :Init
{
my ($self, $args) = @_;
if (exists($args->{'MY_PARAM'})) {
$self->set(\@my_field, $args->{'MY_PARAM'});
}
}
...
}
An object for this class would be created like this:
my $obj = My::Class->new('MY_DATA' => $dat,
'MY_PARAM' => $parm);
So what happens here? First of all, $dat gets placed in the object's @my_data field, because the MY_DATA key is specified in the :Arg attribute for that field. โจ
Then _init gets invoked with arguments consisting of the object (i.e., $self) and a hash ref consisting only of { 'MY_PARAM' => $param }, because the key MY_PARAM is specified in the :InitArgs hash. _init checks that the parameter MY_PARAM exists in the hash ref, and then (since it does exist) adds $parm to the object's @my_field field.
Setting Data
Data processed by the :Init subroutine can be placed directly into the class's field arrays (hashes) using the object's ID (i.e., $$self):
$my_field[$$self] = $args->{'MY_PARAM'};
BUT โ and this is the strong rec, as shown in the example above โ it's strongly recommended that you use the ->set() method:
$self->set(\@my_field, $args->{'MY_PARAM'});
because that's the one that handles converting the data to a shared format when it's needed for applications using threads::shared. ๐ง
All Parameters
The :InitArgs hash and the :Arg attribute on fields act like filters, constraining which initialization parameters do and don't get sent to the :Init subroutine. BUT if a class doesn't have an :InitArgs hash and doesn't use the :Arg attribute on any of its fields, then its :Init subroutine (if it exists, obvi) will get all the initialization parameters supplied to the ->new() method. everything, all at once, no filter ๐
Mandatory Parameters
Field-specific parameters can be declared mandatory like this:
my @data :Field
:Arg('Name' => 'data', 'Mandatory' => 1);
If a mandatory parameter is missing from the argument list to ->new(), an error is generated. it's giving consequences ๐ญ
For :Init handled parameters, you'd use:
my %init_args :InitArgs = (
'data' => {
'Mandatory' => 1,
},
);
Mandatory can be abbreviated to Mand, and Required or Req are synonymous. so many nicknames, she contains multitudes ๐
Default Values
For optional parameters, you can specify defaults for field-specific parameters using either of these syntaxes:
my @data :Field
:Arg('Name' => 'data', 'Default' => 'foo');
my @info :Field :Arg(info) :Default('bar');
If an optional parameter with a specified default is missing from the argument list to ->new(), then the default gets assigned to the field when the object is created (and that happens before the :Init subroutine, if there is one, is called).
The format for :Init handled parameters is:
my %init_args :InitArgs = (
'data' => {
'Default' => 'foo',
},
);
In this case, if the parameter is missing from the argument list to ->new(), then the parameter key gets paired with the default value and added to the :Init argument hash ref (e.g., { 'data' => 'foo' }).
Fields can also be assigned a default value even if they're not associated with an initialization parameter:
my @hash :Field :Default({});
my @tuple :Field :Default([1, 'bar']);
Note: when using :Default, the value must be properly structured Perl code (e.g., strings have to be quoted, like illustrated above). no shortcuts bestie ๐
Default and :Default may be abbreviated to Def and :Def respectively.
Generated Default Values
You can also generate default values on a per-object basis by using code in the :Default directive. ๐
my @IQ :Field :Default(50 + rand 100);
my @ID :Field :Default(our $next; ++$next);
The above, for example, will initialize the IQ attribute of each new object to a different random number, while its ID attribute gets initialized with a sequential integer. obsessed with the range honestly โจ
The code in a :Default specifier can also refer to the object being initialized, either as $_[0] or as $self. For example:
my @unique_ID :Field :Default($self->gen_unique_ID);
โ ๏ธ Any code specified as a default will not have access to the surrounding lexical scope. For example, this will not work:
my $MAX = 100;
my $MIN = 0;
my @bar :Field
:Default($MIN + rand($MAX-$MIX)); # Error
For anything lexical or complex, you should factor the initializer out into a utility subroutine:
sub _rand_max :Restricted
{
$MIN + rand($MAX-$MIX)
}
my @bar :Field
:Default(_rand_max);
When you're specifying a generated default using the Default tag inside an :Arg directive, you'll need to wrap the code in a sub { }, and $_[0] (but not $self) can be used to access the object being initialized:
my @baz :Field
:Arg(Name => 'baz', Default => sub { $_[0]->biz });
System functions need to be wrapped in sub { } too, bestie, same energy:
my @rand :Field
:Type(numeric)
:Arg(Name => 'Rand', Default => sub { rand });
And you can reach subroutines through a code reference, like:
my @data :Field
:Arg(Name => 'Data', Default => \&gen_default);
But honestly?? All of the above is extra for no reason, because the :Default directive does the exact same thing and looks so much cuter:
my @baz :Field :Arg(baz) :Default($self->biz);
my @rand :Field :Arg(Rand) :Default(rand) :Type(numeric);
my @data :Field :Arg(Data) :Default(gen_default);
And if you're doing generated defaults inside the :InitArgs hash, you gotta use the same syntax types as the Default tag in an :Arg directive โ no shortcuts, sorry babe:
my %init_args :InitArgs = (
'Baz' => {
'Default' => sub { $_[0]->biz },
},
'Rand' => {
'Default' => sub { rand },
},
'Data' => {
'Default' => \&gen_default,
},
);
โจ Sequential defaults โจ
So in the last section there was one example that is lowkey not as safe or as convenient as it should be, and I need you to hear me on this:
my @ID :Field :Default(our $next; ++$next);
The issue? That shared variable $next doing all the tracking for ID values. Because it has to survive between calls, it's forced to be a package variable โ unless you're on Perl 5.10 or later, where it could be a state variable instead:
use feature 'state';
my @ID :Field :Default(state $next; ++$next);
The package-variable version is unsafe, fr fr, because literally anyone could spoof ID numbers just by reassigning that globally accessible variable:
$MyClass::next = 0; # Spoof the next object
my $obj = MyClass->new; # Object now has ID 1
The state-variable version dodges that problem entirely, but even that version is more complicated (and therefore more error-prone) than it needs to be. ๐ญ
Enter the :SequenceFrom directive, which you can abbreviate to :SeqFrom or just :Seq โ it makes saying "this attribute's default comes from a linearly increasing sequence" sooo much easier. That ID example from before? Rewritten as:
my @ID :Field :SequenceFrom(1);
This directive automatically creates a hidden variable, initializes it to the initial value you specified, generates a sequence starting at that initial value, and then uses successive elements of that sequence every single time a default value is needed for that attribute during object creation. Iconic behavior honestly.
If the initial value is a scalar, the default sequence is generated by computing $previous_value++. If it's an object, it's generated by calling $obj->next() (or by calling $obj++ if the object doesn't have a next() method).
Which makes it stupidly simple to create a series of objects with attributes defaulting to simple numeric, alphabetic, or alphanumeric sequences, or to whatever sequence some iterator object of yours produces:
my @ID :Field :SeqFrom(1); # 1, 2, 3...
my @ID :Field :SeqFrom('AAA'); # 'AAA', 'AAB', 'AAC'...
my @ID :Field :SeqFrom('A01'); # 'A01', 'A02', 'A03'...
my @ID :Field :SeqFrom(ID_Iterator->new); # ->next, ->next, ->next...
In every other respect, a :SequenceFrom directive is just like a :Default. For example, it can vibe with the :Arg directive like this:
my @ID :Field :Arg(ID) :SeqFrom(1);
But NOT as a tag inside the :Arg directive:
my @ID :Field :Arg('Name' => 'ID', 'SeqFrom' => 1) # WRONG
Bestie. Do not do that. It is marked # WRONG for a reason. ๐
And for the :InitArgs hash? You're gonna have to roll your own sequential defaults if you need them:
use feature 'state';
my %init_args :InitArgs = (
'Counter' => {
'Default' => sub { state $next; ++$next }
},
);
โจ Parameter Name Matching โจ
Instead of relying on exact matches to parameter keys in the ->new() argument list, you can hand it a regular expression to match them against field-specific parameters:
my @param :Field
:Arg('Name' => 'param', 'Regexp' => qr/^PARA?M$/i);
With this, the parameter's key could be any of: PARAM, PARM, Param, Parm, param, parm, and so on. And this next one would result in $data getting placed into My::Class's @param field during object creation:
my $obj = My::Class->new('Parm' => $data);
For :Init handled parameters, you'd do the same thing basically:
my %init_args :InitArgs = (
'Param' => {
'Regex' => qr/^PARA?M$/i,
},
);
Here the match results in { 'Param' => $data } being sent to the :Init subroutine as the argument hash. Heads up: the :InitArgs hash key gets substituted in for the original argument key. Which means you never have to do any parameter key pattern matching inside the :Init subroutine. We love a convenience. ๐
Oh and Regexp can be abbreviated to Regex or Re.
โจ Object Pre-initialization โจ
Sometimes a child class needs to send a parameter up to a parent class as part of object initialization. That's what a :PreInit labeled subroutine in the child class is for. These subroutines, if they exist, get called in order from the bottom of the class hierarchy upward (i.e., child classes first).
The subroutine should expect two arguments: the newly created (uninitialized) object (i.e., $self), and a hash ref of all the parameters from the ->new() method call โ including any additional parameters added by other :PreInit subroutines.
sub pre_init :PreInit
{
my ($self, $args) = @_;
...
}
Now, that parameter hash ref will not be exactly as supplied to ->new() โ it gets flattened into a single hash ref. So for example:
my $obj = My::Class->new(
'param_X' => 'value_X',
{
'param_A' => 'value_A',
'param_B' => 'value_B',
},
'My::Class' => { 'param' => 'value' },
);
would produce
{
'param_X' => 'value_X',
'param_A' => 'value_A',
'param_B' => 'value_B',
'My::Class' => { 'param' => 'value' }
}
as the hash ref handed to the :PreInit subroutine.
From there, the :PreInit subroutine can add, modify, or even straight up remove any parameters from the hash ref as needed for its purposes. Once all the :PreInit subroutines have run, object initialization proceeds using the resulting parameter hash.
๐จ Important: the:PreInitsubroutine should not try to set data in its class's fields or in other classes' fields (like, say, via set methods), because those changes will just get overwritten during the initialization phase that follows pre-initialization.:PreInitis only there for modifying initialization parameters before initialization. That's it. That's the whole job.
โจ Initialization Sequence โจ
For the most part, you can think of object initialization as moving from parent classes down through child classes. Which means calling child class methods from a parent class during object initialization may not work, because the object won't have been fully initialized in the child classes yet. ๐ฌ
Knowing the order of events during object initialization helps you figure out when that's actually safe to do:
- The scalar reference for the object is created, populated with an "Object ID", and blessed into the appropriate class.
:PreInitsubroutines are called in order from the bottom of the class hierarchy upward (i.e., child classes first).- From the top of the class hierarchy downward (i.e., parent classes first), "Default Values" are assigned to fields. (These may be overwritten by subsequent steps below.)
- From the top of the class hierarchy downward, parameters to the
->new()method are processed for:Argfield attributes and entries in the:InitArgshash:- "Parameter Preprocessing" is performed.
- Checks for "Mandatory Parameters" are made.
- "Default Values" specified in the
:InitArgshash are added for subsequent processing by the:Initsubroutine. - Type checking is performed.
- "Field-Specific Parameters" are assigned to fields.
- From the top of the class hierarchy downward,
:Initsubroutines are called with parameters specified in the:InitArgshash. - Checks are made for any parameters to
->new()that were not handled in the above. (See next section.)
โจ Unhandled Parameters โจ
It is an error to include any parameters to the ->new() method that aren't handled by at least one class in the hierarchy. And the main reason for that rule? Catching typos in parameter names:
my $obj = Person->new('nane' => 'John'); # Should be 'name'
The only time these checks for unhandled parameters don't happen is when at least one class in the hierarchy doesn't have an :InitArgs hash, doesn't use the :Arg attribute on any of its fields, and uses an :Init subroutine for processing parameters. In that situation, it's just not possible for Object::InsideOut to figure out which (if any) of the parameters aren't handled by the :Init subroutine.
If you want to soften this up, drop the following construct at the start of your application:
BEGIN {
no warnings 'once';
$OIO::Args::Unhandled::WARN_ONLY = 1;
}
Then unhandled parameters will only generate warnings instead of causing exceptions to be thrown. Gentle parenting, but for your constructor. ๐ซถ
โจ Modifying :InitArgs โจ
For performance purposes, Object::InsideOut normalizes each class's :InitArgs hash by creating keys in the form of _X for the various options it handles (e.g., _R for 'Regexp').
So if a class has the unusual requirement to modify its :InitArgs hash during runtime, it must renormalize the hash after making those changes by invoking Object::InsideOut::normalize() on it, so Object::InsideOut actually picks up what you did:
Object::InsideOut::normalize(\%init_args);
โจ ACCESSOR GENERATION โจ
Okay so bestie, accessors are just object methods that get data out of and put data into an object. You can absolutely hand-write your own accessor code โ nobody's stopping you โ but it gets tedious real fast, especially if your class has like a million fields. Object::InsideOut is here to do it for you. ๐
๐ธ Basic Accessors ๐ธ
A get accessor is super basic: it literally just hands you back the value of an object's field:
my @data :Field;
sub fetch_data
{
my $self = shift;
return ($data[$$self]);
}
and you'd use it like this:
my $data = $obj->fetch_data();
To make Object::InsideOut generate that get accessor for you, slap a :Get attribute onto the field declaration, and put the accessor's name in parentheses:
my @data :Field :Get(fetch_data);
Same energy for a set accessor, which puts data into an object's field. The set accessors Object::InsideOut generates will check that they're called with at least one argument (yes ma'am, no silent nonsense). You declare them with the :Set attribute:
my @data :Field :Set(store_data);
Some programmers roll with the convention of naming get and set accessors with get_ and set_ prefixes. Those standard accessors come from the :Standard attribute (abbreviated :Std):
my @data :Field :Std(data);
which is exactly the same as:
my @data :Field :Get(get_data) :Set(set_data);
Other programmers prefer one single combination accessor doing both jobs: no arguments = it gets, one argument = it sets. Object::InsideOut generates those with the :Accessor attribute. (Abbreviate to :Acc, or use :Get_Set or :Combined or :Combo or even Mutator.) For example:
my @data :Field :Acc(data);
And that generated accessor gets used like:
$obj->data($val); # Puts data into the object's field
my $data = $obj->data(); # Fetches the object's field data
๐ Set Accessor Return Value ๐
For every automatically generated method that does a set operation, the default return value is the value being set โ i.e. the new value.
You can pick the set accessor's return value with the Return attribute parameter (abbreviate it to Ret). So to explicitly spell out that default behaviour:
my @data :Field :Set('Name' => 'store_data', 'Return' => 'New');
You can also say the accessor should return the old (previous) value โ or undef if it was never set:
my @data :Field :Acc('Name' => 'data', 'Ret' => 'Old');
You may use Previous, Prev or Prior as synonyms for Old. ๐
And finally, you can say the accessor should return the object itself:
my @data :Field :Std('Name' => 'data', 'Ret' => 'Object');
Object can be abbreviated to Obj, and it's also synonymous with Self.
๐ฆ Method Chaining ๐ฆ
The most obvious case for method chaining is when a field is holding an object: a method for the stored object chains right off the get accessor call that fetched it:
$obj->get_stored_object()->stored_object_method()
Chaining off set accessors works based on their return value (see above, bestie, it all connects ๐). Here's an example with a set accessor that returns the new value:
$obj->set_stored_object($stored_obj)->stored_object_method()
so the set_stored_object() call stores the new object, returns it too, and then stored_object_method() gets invoked via the stored/returned object. Same thing works for set accessors that return the old value โ except in that case the chained method is invoked via the previously stored (and now returned) object.
If the Want module (version 0.12 or later) is available, then Object::InsideOut also tries to do the right thing with method chaining for set accessors that don't store/return objects. In that case, the object used to invoke the set accessor also gets used to invoke the chained method (just as though the set accessor were declared with 'Return' => 'Object'):
$obj->set_data('data')->do_something();
To use this feature, just add use Want; to the beginning of your application. โจ
Note though: this special handling does not apply to get accessors, nor to combination accessors invoked without an argument (i.e., when used as a get accessor). Those ones must return objects for method chaining to succeed.
๐ :lvalue Accessors ๐
As documented in "Lvalue subroutines" in perlsub, an :lvalue subroutine returns a modifiable value. That modifiable value can then, for example, be used on the left-hand side (hence "LVALUE") of an assignment statement, or a substitution regular expression.
For Perl 5.8.0 and later, Object::InsideOut supports generating :lvalue accessors so that using them in an "LVALUE" context sets the value of the object's field. Just add 'lvalue' => 1 to the set accessor's attribute. (lvalue may be abbreviated to lv.)
Plus, :Lvalue (or its abbreviation :lv) can be used for a combined get/set :lvalue accessor. In other words, these two are equivalent:
:Acc('Name' => 'email', 'lvalue' => 1)
:Lvalue(email)
Here's a detailed example, no cap:
package Contact; {
use Object::InsideOut;
# Create separate a get accessor and an :lvalue set accessor
my @name :Field
:Get(name)
:Set('Name' => 'set_name', 'lvalue' => 1);
# Create a standard get_/set_ pair of accessors
# The set_ accessor will be an :lvalue accessor
my @phone :Field
:Std('Name' => 'phone', 'lvalue' => 1);
# Create a combined get/set :lvalue accessor
my @email :Field
:Lvalue(email);
}
package main;
my $obj = Contact->new();
# Use :lvalue accessors in assignment statements
$obj->set_name() = 'Jerry D. Hedden';
$obj->set_phone() = '800-555-1212';
$obj->email() = 'jdhedden AT cpan DOT org';
# Use :lvalue accessor in substitution regexp
$obj->email() =~ s/ AT (\w+) DOT /\@$1./;
# Use :lvalue accessor in a 'substr' call
substr($obj->set_phone(), 0, 3) = '888';
print("Contact info:\n");
print("\tName: ", $obj->name(), "\n");
print("\tPhone: ", $obj->get_phone(), "\n");
print("\tEmail: ", $obj->email(), "\n");
Using :lvalue accessors requires installing the Want module (version 0.12 or later) from CPAN. Go read "Lvalue subroutines:" in Want for more info. ๐ซถ
:lvalue accessors also act like regular set accessors in that they can accept arguments, return values, and so on:
my @pri :Field
:Lvalue('Name' => 'priority', 'Return' => 'Old');
...
my $old_pri = $obj->priority(10);
:lvalue accessors can be used in method chains too. Slay.
Caveats: While still classified as experimental, Perl's support for :lvalue subroutines has been around since 5.6.0, and a good number of CPAN modules make use of them.
By definition, because :lvalue accessors return the location of a field, they break encapsulation. As a result, some OO advocates eschew the use of :lvalue accessors. ๐
:lvalue accessors are slower than corresponding non-lvalue accessors. This is because more code is needed to handle all the diverse ways :lvalue accessors may be used. (The author says: "I've done my best to optimize the generated code.") For example, here's the code generated for a simple combined accessor:
*Foo::foo = sub {
return ($$field[${$_[0]}]) if (@_ == 1);
$$field[${$_[0]}] = $_[1];
};
And here's the corresponding code for an :lvalue combined accessor:
*Foo::foo = sub :lvalue {
my $rv = !Want::want_lvalue(0);
Want::rreturn($$field[${$_[0]}]) if ($rv && (@_ == 1));
my $assign;
if (my @args = Want::wantassign(1)) {
@_ = ($_[0], @args);
$assign = 1;
}
if (@_ > 1) {
$$field[${$_[0]}] = $_[1];
Want::lnoreturn if $assign;
Want::rreturn($$field[${$_[0]}]) if $rv;
}
((@_ > 1) && (Want::wantref() eq 'OBJECT') &&
!Scalar::Util::blessed($$field[${$_[0]}]))
? $_[0] : $$field[${$_[0]}];
};
๐ ALL-IN-ONE ๐
Parameter naming and accessor generation can be combined, bestie:
my @data :Field :All(data);
This is syntactic shorthand for:
my @data :Field :Arg(data) :Acc(data);
If you want the accessor to be :lvalue, use:
my @data :Field :LV_All(data);
If standard accessors are what you're after, use:
my @data :Field :Std_All(data);
Attribute parameters affecting the set accessor may also be used. For example, if you want standard accessors with an :lvalue set accessor:
my @data :Field :Std_All('Name' => 'data', 'Lvalue' => 1);
If you want a combined accessor that returns the old value on set operations:
my @data :Field :All('Name' => 'data', 'Ret' => 'Old');
And so on and so on. ๐ซ
If you need to add attribute parameters that affect the:Argportion (e.g.,Default,Mandatory, etc.), then you cannot use:All. Fall back to using the separate attributes. For example:
my @data :Field :Arg('Name' => 'data', 'Mand' => 1)
:Acc('Name' => 'data', 'Ret' => 'Old');
๐ READONLY FIELDS ๐
If you want to declare a read-only field โ i.e., one that can be initialized and retrieved, but which doesn't have a set accessor:
my @data :Field :Arg(data) :Get(data);
there's a syntactic shorthand for that, too:
my @data :Field :ReadOnly(data);
or just:
my @data :Field :RO(data);
If a standard get accessor is what you want, use:
my @data :Field :Std_RO(data);
For obvious reasons, attribute parameters affecting the set accessor cannot be used with read-only fields, and :ReadOnly cannot be combined with :LValue. ๐ซ
As with :All, if you need to add attribute parameters that affect the :Arg portion then you can't use the :RO shorthand: fall back to using the separate attributes in those cases. For example:
my @data :Field :Arg('Name' => 'data', 'Mand' => 1)
:Get('Name' => 'data');
๐ DELEGATORS ๐
Okay bestie, so you already know how to autogenerate accessors for a field. But plot twist โ you can also autogenerate delegators to that field. And a delegator is just an accessor that forwards its call to one of the object's fields. Like a little messenger ๐ง that takes your call and hands it straight to the field.
So say your Car object has an @engine field, right? Then you might need to send all your acceleration requests to the Engine object chillin' in that field. Same vibe for braking โ those requests gotta be forwarded to Car's field that holds the Brakes object:
package Car; {
use Object::InsideOut;
my @engine :Field :Get(engine);
my @brakes :Field :Get(brakes);
sub _init :Init(private) {
my ($self, $args) = @_;
$self->engine(Engine->new());
$self->brakes(Brakes->new());
}
sub accelerate {
my ($self) = @_;
$self->engine->accelerate();
}
sub decelerate {
my ($self) = @_;
$self->engine->decelerate();
}
sub brake {
my ($self, $foot_pressure) = @_;
$self->brakes->brake($foot_pressure);
}
}
But now if the Car needs to forward other method calls to its Engine or Brakes? Bestie, that gets tedious, repetitive, and error-prone sooo fast. ๐ So instead, you just tell Object::InsideOut that a particular method should be automatically forwarded to a particular field โ using a :Handles attribute:
package Car; {
use Object::InsideOut;
my @engine :Field
:Get(engine)
:Handles(accelerate, decelerate);
my @brakes :Field
:Get(brakes)
:Handles(brake);
sub _init :Init(private) {
my ($self, $args) = @_;
$self->engine(Engine->new());
$self->brakes(Brakes->new());
}
}
This option generates and installs a single delegator method for each of its arguments, so example two has exactly the same effect as example one. Periodt. โจ The delegator simply calls the corresponding method on the object stored in the field, passing it the same argument list it received. Clean, no notes.
Sometimes tho, you may need to delegate a particular method to a field โ but under a different name. ๐ Like, if the Brake class provides an engage() method rather than a brake() method, then you'd need Car::brake() to be implemented as:
sub brake {
my ($self, $foot_pressure) = @_;
$self->brakes->engage($foot_pressure);
}
And you can achieve that with the :Handles attribute, like so:
my @brakes :Field
:Get(brakes)
:Handles(brake-->engage);
The long arrow version still creates a delegator method brake(), but it makes that method delegate to your Brakes object by calling its engage() method instead. The way that arrow just reroutes everythingโฆ iconic. ๐
If you're delegating a large number of methods to a particular field, those :Handles declarations get tedious real quick:
my @onboard_computer :Field :Get(comp)
:Type(Computer::Onboard)
:Handles(engine_monitor engine_diagnostics)
:Handles(engine_control airbag_deploy)
:Handles(GPS_control GPS_diagnostics GPS_reset)
:Handles(climate_control reversing_camera)
:Handles(cruise_control auto_park)
:Handles(iPod_control cell_phone_connect);
And of course, every single time the interface of the Computer::Onboard class changes, you have to change those :Handles declarations too. ๐ญ Not the maintenance burden.
So sometimes, all you really wanna say is: "This field should handle anything it can handle." And to do that, you write:
my @onboard_computer :Field :Get(comp)
:Type(Computer::Onboard)
:Handles(Computer::Onboard);
That is โ if a :Handles directive is given a name that includes a ::, it treats that name as a class name, not a method name. Then it checks that class's metadata (peep INTROSPECTION for the tea), retrieves a list of all the method names from the class, and uses that as the list of method names to delegate. Chef's kiss. ๐ซถ
Now here's the important difference: unlike an explicit :Handles( method_name ), a :Handles( Class::Name ) is tolerant of name collisions. If any method of Class::Name has the same name as another method or delegator that has already been installed in the current class, then :Handles just silently ignores that particular method โ it doesn't try to replace the existing one. In other words, a :Handles(Class::Name) won't install a delegator to a method in Class::Name if that method is already being handled somewhere else by the current class. She's polite like that. ๐
For classes that don't have a :: in their name (e.g., DateTime and POE), just append a :: to the class name:
my @init_time :Field :Get(init_time)
:Type( DateTime )
:Default( DateTime->now() )
:Handles( DateTime:: );
Heads up bestie: when using the class-based version of :Handles, every method is delegated with its name unchanged. If some of the object's methods should be delegated under different names, you have to specify that explicitly โ and beforehand:
my @onboard_computer :Field :Get(comp) :Type(Computer::Onboard)
# rename this method when delegating...
:Handles( iPod_control-->get_iPod )
# delegate everything else with names unchanged...
:Handles( Computer::Onboard );
"Handles" may be abbreviated to "Handle" or "Hand". Short queen energy. โจ
NOTES: Failure to add the appropriate object to the delegation field will lead to errors such as: Can't call method "bar" on an undefined value.
Typos in:Handlesattribute declarations will lead to errors such as:Can't locate object method "bat" via package "Foo".Adding an object of the wrong class to the delegation field will lead to the same error โ but you can avoid that by adding a:Typeattribute for the appropriate class. ๐
๐ PERMISSIONS ๐
Restricted and Private Accessors
By default, automatically generated accessors can be called at any time. In other words, their access permission is public. Everyone's invited to the party. ๐
But if you want, accessors can be made restricted โ in which case they can only be called from within the class and any child classes in the hierarchy that are derived from it โ or private โ such that they can only be called from within the accessors' class. Here are examples of the syntax for adding permissions:
my @data :Field :Std('Name' => 'data', 'Permission' => 'private');
my @info :Field :Set('Name' => 'set_info', 'Perm' => 'restricted');
my @internal :Field :Acc('Name' => 'internal', 'Private' => 1);
my @state :Field :Get('Name' => 'state', 'Restricted' => 1);
When creating a standard pair of get_/set_ accessors, the permission setting is applied to both accessors. If you need different permissions on the two accessors, then you'll have to use separate :Get and :Set attributes on the field. It's giving "different strokes for different accessors". ๐
# Create a private set method
# and a restricted get method on the 'foo' field
my @foo :Field
:Set('Name' => 'set_foo', 'Priv' => 1)
:Get('Name' => 'get_foo', 'Rest' => 1);
# Create a restricted set method
# and a public get method on the 'bar' field
my %bar :Field
:Set('Name' => 'set_bar', 'Perm' => 'restrict')
:Get(get_bar);
"Permission" may be abbreviated to "Perm"; "Private" may be abbreviated to "Priv"; and "Restricted" may be abbreviated to "Restrict". Slay, so many cute little nicknames. ๐
Restricted and Private Methods
Same vibe as what we just described above โ access to methods can be narrowed by using the :Restricted and :Private attributes.
sub foo :Restricted
{
my $self = shift;
...
}
Without either of these attributes, most methods have public access. And if you want, you can explicitly label them with the :Public attribute. ๐
Exemptions
It is also possible to specify classes that are exempt from the Restricted and Private access permissions (i.e., the method may be called from those classes as well). Your VIP list, basically. ๐
my %foo :Field
:Acc('Name' => 'foo', 'Perm' => 'Restrict(Exempt::Class)')
:Get(get_bar);
sub bar :Private(Some::Class, Another::Class)
{
my $self = shift;
...
}
An example of when this might be needed is with delegation mechanisms. (iykyk ๐)
Hidden Methods
For subroutines marked with the following attributes (most of which are discussed later in this document):
:ID:PreInit:Init:Replicate:Destroy:Automethod:Dumper:Pumper:MOD_*_ATTRS:FETCH_*_ATTRS
Object::InsideOut normally renders them uncallable (hidden) to class and application code (since they should normally only be needed by Object::InsideOut itself). She's shy, okay?? ๐
But if you need it, this behavior can be overridden by adding the "Public", "Restricted" or "Private" attribute parameters:
sub _init :Init(private) # Callable from within this class
{
my ($self, $args) = @_;
...
}
Restricted and Private Classes
Permission for object creation on a class can be narrowed by adding a :Restricted or :Private flag to its use Object::InsideOut ... declaration. This basically adds :Restricted/:Private permissions on the ->new() method for that class. Exemptions are also supported. ๐ซถ
package Foo; {
use Object::InsideOut;
...
}
package Bar; {
use Object::InsideOut 'Foo', ':Restricted(Ping, Pong)';
...
}
In the above, class Bar inherits from class Foo, and its constructor is restricted to itself, classes that inherit from Bar, and the classes Ping and Pong. Keeping it in the family, no cap. ๐
Now here's the catch โ as constructors are inherited, any class that inherits from Bar would also be a restricted class. To overcome this, any child class would need to add its own permission declaration:
package Baz; {
use Object::InsideOut qw/Bar :Private(My::Class)/;
...
}
Here, class Baz inherits from class Bar, and its constructor is restricted to itself (i.e., private) and class My::Class.
Inheriting from a :Private class is permitted, but objects cannot be created for that class unless it has a permission declaration of its own:
package Zork; {
use Object::InsideOut qw/:Public Baz/;
...
}
Here, class Zork inherits from class Baz, and its constructor has unrestricted access. ๐
In general, don't use the :Public declaration for a class except to overcome constructor permissions inherited from parent classes. That's the one job. ๐
๐ TYPE CHECKING ๐
okay bestie, so Object::InsideOut can be directed to add type-checking code to the set/combined accessors it generates, and it can also do type checking on object initialization parameters. no cap, it's giving safety rails for your data. ๐
๐ Field Type Checking ๐
Type checking for a field? You specify it by adding the :Type attribute to the field declaration, like:
my @count :Field :Type(numeric);
my @objs :Field :Type(list(My::Class));
The :Type attribute results in type checking code being added to set/combined accessors generated by Object::InsideOut, and it will perform type checking on object initialization parameters processed by the :Arg attribute. periodt.
Available Types are:
'scalar'โ permits anything that is not a reference. simple.'numeric'โ can also be specified as"Num"or"Number". This usesScalar::Util::looks_like_number()to test the input value. โจ'list'or'array'/'list(_subtype_)'or'array(_subtype_)'โ This type permits an accessor to accept multiple values (which are then placed in an array ref) or a single array ref. For object initialization parameters, it permits a single value (which is then placed in an array ref) or an array ref. When specified, the contents of the resulting array ref are checked against the specified subtype:'scalar'โ Same as for the basic type above.'numeric'โ Same as for the basic type above.- A class name โ Same as for the basic type below.
- A reference type โ Any reference type (in all caps) as returned by
ref()).
'ARRAY_ref'/'ARRAY_ref(_subtype_)'โ This specifies that only a single array reference is permitted. Can also be specified as"ARRAYref". When specified, the contents of the array ref are checked against the specified subtype as per the above.'HASH'โ This type permits an accessor to accept multiple"key => value"pairs (which are then placed in a hash ref) or a single hash ref. For object initialization parameters, only a single ref is permitted.'HASH_ref'โ This specifies that only a single hash reference is permitted. Can also be specified as"HASHref".'SCALAR_ref'โ This type permits an accessor to accept a single scalar reference. Can also be specified as"SCALARref".- A class name โ This permits only an object of the specified class, or one of its sub-classes (i.e., type checking is done using
"->isa()"). For example,"My::Class". The class name"UNIVERSAL"permits any object. The class name"Object::InsideOut"permits any object generated by an Object::InsideOut class. - Other reference type โ This permits only a reference of the specified type (as returned by
ref()). The type must be specified in all caps. For example,"CODE".
The :Type attribute can also be supplied with a code reference to provide custom type checking. The code ref may either be in the form of an anonymous subroutine, or a fully-qualified subroutine name. The result of executing the code ref on the input argument should be a boolean value. Here's some examples:
package My::Class; {
use Object::InsideOut;
# Type checking using an anonymous subroutine
# (This checks that the argument is an object)
my @data :Field :Type(sub { Scalar::Util::blessed($_[0]) })
:Acc(data);
# Type checking using a fully-qualified subroutine name
my @num :Field :Type(\&My::Class::positive)
:Acc(num);
# The type checking subroutine may be made 'Private'
sub positive :Private
{
return (Scalar::Util::looks_like_number($_[0]) &&
($_[0] > 0));
}
}
๐ Type Checking on ":Init" Parameters ๐
For object initialization parameters that are sent to the :Init subroutine during object initialization, the parameter's type can be specified in the :InitArgs hash for that parameter using the same types as specified in the previous section. For example:
my %init_args :InitArgs = (
'COUNT' => {
'Type' => 'numeric',
},
'OBJS' => {
'Type' => 'list(My::Class)',
},
);
One exception involves custom type checking: If referenced in an :InitArgs hash, the type checking subroutine cannot be made :Private:
package My::Class; {
use Object::InsideOut;
sub check_type # Cannot be :Private
{
...
}
my %init_args :InitArgs = (
'ARG' => {
'Type' => \&check_type,
},
);
...
}
Also, as shown, it also doesn't have to be a fully-qualified name. slay. ๐
๐ฅ CUMULATIVE METHODS ๐ฅ
Normally, methods with the same name in a class hierarchy are masked (i.e., overridden) by inheritance โ only the method in the most-derived class is called. With cumulative methods, this masking is removed, and the same-named method is called in each of the classes within the hierarchy. The return results from each call (if any) are then gathered together into the return value for the original method call. For example,
package My::Class; {
use Object::InsideOut;
sub what_am_i :Cumulative
{
my $self = shift;
my $ima = (ref($self) eq __PACKAGE__)
? q/I was created as a /
: q/My top class is /;
return ($ima . __PACKAGE__);
}
}
package My::Foo; {
use Object::InsideOut 'My::Class';
sub what_am_i :Cumulative
{
my $self = shift;
my $ima = (ref($self) eq __PACKAGE__)
? q/I was created as a /
: q/I'm also a /;
return ($ima . __PACKAGE__);
}
}
package My::Child; {
use Object::InsideOut 'My::Foo';
sub what_am_i :Cumulative
{
my $self = shift;
my $ima = (ref($self) eq __PACKAGE__)
? q/I was created as a /
: q/I'm in class /;
return ($ima . __PACKAGE__);
}
}
package main;
my $obj = My::Child->new();
my @desc = $obj->what_am_i();
print(join("\n", @desc), "\n");
produces:
My top class is My::Class
I'm also a My::Foo
I was created as a My::Child
When called in a list context (as in the above), the return results of cumulative methods are accumulated, and returned as a list.
In a scalar context, a results object is returned that segregates the results by class for each of the cumulative method calls. Through overloading, this object can then be dereferenced as an array, hash, string, number, or boolean. For example, the above could be rewritten as:
my $obj = My::Child->new();
my $desc = $obj->what_am_i(); # Results object
print(join("\n", @{$desc}), "\n"); # Dereference as an array
The following uses hash dereferencing:
my $obj = My::Child->new();
my $desc = $obj->what_am_i();
while (my ($class, $value) = each(%{$desc})) {
print("Class $class reports:\n\t$value\n");
}
and produces:
Class My::Class reports:
My top class is My::Class
Class My::Child reports:
I was created as a My::Child
Class My::Foo reports:
I'm also a My::Foo
As illustrated above, cumulative methods are tagged with the :Cumulative attribute (or :Cumulative(top down)), and propagate from the top down through the class hierarchy (i.e., from the parent classes down through the child classes). If tagged with :Cumulative(bottom up), they will propagated from the object's class upward through the parent classes. ๐
๐ CHAINED METHODS ๐
Okay bestie, so besides :Cumulative, Object::InsideOut also lets you make methods that are chained together โ meaning their return values get passed as input arguments to other, similarly-named methods up and down the same class hierarchy. It's literally like piping them into each other. pipeline energy โจ
Say you had a method called format_name that formats text 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 somewhere else you've got a second class that fixes up 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 then you're like, "okay but I want to do my own formatting first, and then let all the parent methods do their thing." Normally, with just one parent class, you'd just call it directly with $self->SUPER::format_name($name) โ easy. But if you've got more than one parent class, you'd have to explicitly call each method one by one:
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 though? You just slap the :Chained attribute on each class's format_name method and they chain up automatically. no cap ๐ซถ
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 someone's name into format_name on Customer would remove the leading and trailing whitespace, then properly case the name, and finally squash the whitespace down to a single space. The resulting $name gets returned to whoever called it:
my ($name) = $obj->format_name($name_raw);
โ ๏ธ Unlike:Cumulativemethods,:Chainedmethods always return an array โ even when there's only one value coming back. So you should always call:Chainedmethods in an array context, like the example above. don't forget, bestie ๐ญ
The default direction is chaining from the parent classes at the top of the class hierarchy down through the child classes. You can use the attribute :Chained(top down) to make that explicit if you want it spelled out. ๐
And if you label the method with the :Chained(bottom up) attribute, then the chained methods get called starting with the object's class and working upward through the parent classes in the class hierarchy โ basically the same way :Cumulative(bottom up) works. ๐ฆ
๐งฉ ARGUMENT MERGING ๐งฉ
So like, as we mentioned back under "Object Creation", the ->new() method can take parameters passed in as combinations of key => value pairs and/or hash refs:
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',
},
);
All those parameters get merged into a single hash ref before they're processed. โจ
Adding the :MergeArgs attribute to your methods gives them that same ability. Your method then gets two arguments: the object, and one single hash ref of the merged arguments. For example:
package Foo; {
use Object::InsideOut;
...
sub my_method :MergeArgs {
my ($self, $args) = @_;
my $param = $args->{'param'};
my $data = $args->{'data'};
my $flag = $args->{'flag'};
...
}
}
package main;
my $obj = Foo->new(...);
$obj->my_method( { 'data' => 42,
'flag' => 'true' },
'param' => 'foo' );
it's giving clean, merged, organized. ๐
โ ARGUMENT VALIDATION โ
A bunch of users have asked about argument validation for methods: http://www.cpanforum.com/threads/3204. For that, the recommendation is to use Params::Validate:
package Foo; {
use Object::InsideOut;
use Params::Validate ':all';
sub foo
{
my $self = shift;
my %args = validate(@_, { bar => 1 });
my $bar = $args{bar};
...
}
}
Using Attribute::Params::Validate, you can use attributes for your argument validation specifications:
package Foo; {
use Object::InsideOut;
use Attribute::Params::Validate;
sub foo :method :Validate(bar => 1)
{
my $self = shift;
my %args = @_;
my $bar = $args{bar};
...
}
}
๐ Note that in the above, Perl's :method attribute (all lowercase) is needed.
There's some incompatibility between Attribute::Params::Validate and a few of Object::InsideOut's attributes. Namely: you cannot use :Validate with :Private, :Restricted, :Cumulative, :Chained or :MergeArgs. In those cases, use the validate() function from Params::Validate instead. periodt. ๐
๐ช AUTOMETHODS ๐ช
Okay so there are significant issues with Perl's AUTOLOAD mechanism that make it pretty ill-suited for use in a class hierarchy. So Object::InsideOut implemented its own :Automethod mechanism to overcome all that mess. ๐
Classes that need AUTOLOAD-type capabilities have to provide a subroutine labeled with the :Automethod attribute. The :Automethod subroutine gets called with the object and the arguments from the original method call (same as for AUTOLOAD). And it should return either a subroutine reference that implements the requested method's functionality, or just end with return; to say "nope, I don't know how to handle this one."
Using its own AUTOLOAD subroutine (which gets exported to every class), Object::InsideOut walks through the class tree, calling each :Automethod subroutine as needed, to fulfill an unimplemented method call. ๐ง
The name of the method being called is passed as $_ instead of $AUTOLOAD, and it's not prefixed with the class name. And if your :Automethod subroutine also needs to peek at the $_ from the caller's scope, it's available as $CALLER::_. โจ
Automethods can also be made to act as "CUMULATIVE METHODS" or "CHAINED METHODS". In those cases, the :Automethod subroutine should return two values: the subroutine ref to handle the method call, and a string designating the type of method. That designator has the same form as the attributes used to designate :Cumulative and :Chained methods:
':Cumulative' or ':Cumulative(top down)'
':Cumulative(bottom up)'
':Chained' or ':Chained(top down)'
':Chained(bottom up)'
The following skeletal code shows how an :Automethod subroutine could be structured:
sub _automethod :Automethod
{
my $self = shift;
my @args = @_;
my $method_name = $_;
# This class can handle the method directly
if (...) {
my $handler = sub {
my $self = shift;
...
return ...;
};
### OPTIONAL ###
# Install the handler so it gets called directly next time
# no strict refs;
# *{__PACKAGE__.'::'.$method_name} = $handler;
################
return ($handler);
}
# This class can handle the method as part of a chain
if (...) {
my $chained_handler = sub {
my $self = shift;
...
return ...;
};
return ($chained_handler, ':Chained');
}
# This class cannot handle the method request
return;
}
โ ๏ธ Note: the OPTIONAL code above for installing the generated handler as a method should not be used with:Cumulativeor:Chainedautomethods. don't do it, bestie, I'm begging ๐
โจ OBJECT SERIALIZATION โจ
๐พ Basic Serialization
my $array_ref = $obj->dump();
my $string = $obj->dump(1);
okay so Object::InsideOut exports a method called ->dump() to every single class that hands you back either a Perl representation or a string representation of the object that called it. bestie, it's giving "let me tell you everything about myself" ๐
The Perl representation comes back when you call ->dump() with no arguments at all. It's an array ref where the first element is the name of the object's class, and the second element is a hash ref holding the object's data. That data hash ref has keys for each of the classes that make up the object's hierarchy. The values for those keys are hash refs with key => value pairs for the object's fields. like, for example:
[
'My::Class::Sub',
{
'My::Class' => {
'data' => 'value'
},
'My::Class::Sub' => {
'life' => 42
}
}
]
The name for an object field (data and life in the example up there) can be set by adding the :Name attribute to the field:
my @life :Field :Name(life);
If you don't use the :Name attribute, then the name for a field is gonna be whichever of these hits first: the name associated with an :All or :Arg attribute, its get method name, its set method name, or โ if literally none of that works โ a string of the form ARRAY(0x...) or HASH(0x...). ๐
When you call it with a true argument, ->dump() returns a string version of the Perl representation using Data::Dumper.
โ ๏ธ heads up: using Data::Dumper directly on an inside-out object will not produce the results you want (it'll just spit out the contents of the scalar ref). also, if inside-out objects are stored inside other structures, a dump of those structures won't contain the contents of the object's fields. so don't do that ๐
In the event of a method naming conflict, the ->dump() method can be called using its fully-qualified name:
my $dump = $obj->Object::InsideOut::dump();
my $obj = Object::InsideOut->pump($data);
Object::InsideOut->pump() takes the output from the ->dump() method and gives you back an object created using that data. if $data is the array ref you got from $obj->dump(), then the data gets inserted directly into the corresponding fields for each class in the object's class hierarchy. if $data is the string you got from $obj->dump(1), then it gets evaled to turn it into an array ref, and then processed the same way. it's giving rebirth โจ๐ฆ
Caveats, bestie, listen up: If any of an object's fields are dumped to field name keys of the formARRAY(0x...)orHASH(0x...)(peep above ๐), then the data will not be reloadable usingObject::InsideOut->pump(). To overcome this problem, the class developer must either add:Nameattributes to the:Fielddeclarations (again, see above), or provide a:Dumper/:Pumperpair of subroutines as described below.
Dynamically altering a class (e.g., using ->create_field()) after objects have been dumped will result in undef fields when pumped back in, regardless of whether or not the added fields have defaults. no cap, they just come back empty ๐ญ
Modifying the output from ->dump(), and then feeding it into Object::InsideOut->pump() will work, but it is not specifically supported. If you know what you're doing, fine, but you're on your own. periodt.
๐ :Dumper Subroutine Attribute
If a class needs special processing to dump its data, then it can provide a subroutine labeled with the :Dumper attribute. This subroutine gets sent the object that is being dumped. It may then return any type of scalar the developer deems appropriate. Usually, this would be a hash ref containing key => value pairs for the object's fields. For example:
my @data :Field;
sub _dump :Dumper
{
my $obj = $_[0];
my %field_data;
$field_data{'data'} = $data[$$obj];
return (\%field_data);
}
Just be sure not to call your :Dumper subroutine dump โ because that's the name of the dump method exported by Object::InsideOut, as explained above. naming collision city ๐
๐ :Pumper Subroutine Attribute
If a class supplies a :Dumper subroutine, it will most likely need to provide a complementary :Pumper labeled subroutine that gets used as part of creating an object from dumped data using Object::InsideOut->pump(). The subroutine gets supplied the new object that is being created, and whatever scalar was returned by the :Dumper subroutine. The corresponding :Pumper for the example :Dumper above would be:
sub _pump :Pumper
{
my ($obj, $field_data) = @_;
$obj->set(\@data, $field_data->{'data'});
}
it's a matching set, bestie, they come as a pair ๐ซถ
๐ง Storable
Object::InsideOut also supports object serialization using the Storable module. There are two methods for specifying that a class can be serialized using Storable. The first method involves adding Storable to the Object::InsideOut declaration in your package:
package My::Class; {
use Object::InsideOut qw(Storable);
...
}
and adding use Storable; in your application. Then you can use the ->store() and ->freeze() methods to serialize your objects, and the retrieve() and thaw() subroutines to de-serialize them.
package main;
use Storable;
use My::Class;
my $obj = My::Class->new(...);
$obj->store('/tmp/object.dat');
...
my $obj2 = retrieve('/tmp/object.dat');
The other method of specifying Storable serialization involves setting a ::storable variable inside a BEGIN block for the class prior to its use:
package main;
use Storable;
BEGIN {
$My::Class::storable = 1;
}
use My::Class;
NOTE: The caveats discussed above for the ->pump() method are also applicable when using the Storable module. same energy, same warnings, bestie ๐ซ
๐ OBJECT COERCION
Object::InsideOut provides support for various forms of object coercion through the overload mechanism. For instance, if you want an object to be usable directly in a string, you would supply a subroutine in your class labeled with the :Stringify attribute:
sub as_string :Stringify
{
my $self = $_[0];
my $string = ...;
return ($string);
}
Then you could do things like:
print("The object says, '$obj'\n");
For a boolean context, you would supply:
sub as_bool :Boolify
{
my $self = $_[0];
my $true_or_false = ...;
return ($true_or_false);
}
and use it in this manner:
if (! defined($obj)) {
# The object is undefined
....
} elsif (! $obj) {
# The object returned a false value
...
}
The following coercion 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. sorry bestie, that one's off the table ๐
๐งฌ CLONING
๐ฏ 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();
When called without arguments, ->clone() creates a shallow copy of the object, meaning that any complex data structures (i.e., array, hash or scalar refs) stored in the object will be shared with its clone. like, they're holding hands ๐ค
Calling ->clone() with a true argument:
my $obj2 = $obj->clone(1);
creates a deep copy of the object such that internally held array, hash or scalar refs are replicated and stored in the newly created clone. fully independent, main character energy ๐
Deep cloning can also be controlled at the field level, and is covered in the next section. keep reading bestie ๐
Note that cloning does not clone internally held objects. For example, if $foo contains a reference to $bar, a clone of $foo will also contain a reference to $bar; not a clone of $bar. If such behavior is needed, it must be provided using a :Replicate subroutine.
๐ง Field Cloning
Object cloning can be controlled at the field level such that specified fields are deeply copied when ->clone() is called without any arguments. This is done by adding the :Deep attribute to the field:
my @data :Field :Deep;
so specific fields get the deep-copy treatment while everything else stays shallow. per-field customization, we love to see it ๐
๐ช WEAK FIELDS
Frequently, it is useful to store weakened references to data or objects in a field. Such a field can be declared as :Weak so that data (i.e., references) set via Object::InsideOut generated accessors, parameter processing using :Arg, the ->set() method, etc., will automatically be weakened after being stored in the field array/hash.
my @data :Field :Weak;
NOTE: If data in a weak field is set directly (i.e., the->set()method is not used), thenweaken()must be invoked on the stored reference afterwards:
$self->set(\@field, $data);
Scalar::Util::weaken($field[$$self]);
(This is another reason why the ->set() method is recommended for setting field data within class code.) do it the easy way, bestie, let the accessor handle it for you ๐ซถโจ
โจ DYNAMIC FIELD CREATION โจ
Okay so normally, fields are declared right there in the class code, like a good little class. But some classes are extra and need to cook up fields on the fly โ say, from inside an :Automethod. Object::InsideOut has your back with a class method for exactly this:
# Dynamically create a hash field with standard accessors
My::Class->create_field('%'.$fld, ":Std($fld)");
So the deets: the first argument is the class you're adding the field to. The second argument is a string with the field's name, and it's gotta be prefixed with either "@" or "%" โ that's how you say "array field" or "hash field" respectively. Everything after that is string arguments declaring accessors and stuff like that. The :Field attribute is assumed, bestie โ you do not need to add it to the attribute list. Look:
My::Class->create_field('@data', ":Type(numeric)",
":Acc(data)");
My::Class->create_field('@obj', ":Type(Some::Class)",
":Acc(obj)",
":Weak");
Heads up: field creation will fail if you try to create an array field inside a class whose hierarchy was declared :hash_only. Not negotiable, it's just not happening. ๐
And here's the moment you've been waiting for โ an :Automethod subroutine that uses dynamic field creation:
package My::Class; {
use Object::InsideOut;
sub _automethod :Automethod
{
my $self = $_[0];
my $class = ref($self) || $self;
my $method = $_;
# Extract desired field name from get_/set_ method name
my ($fld_name) = $method =~ /^[gs]et_(.*)$/;
if (! $fld_name) {
return; # Not a recognized method
}
# Create the field and its standard accessors
$class->create_field('@'.$fld_name, ":Std($fld_name)");
# Return code ref for newly created accessor
no strict 'refs';
return *{$class.'::'.$method}{'CODE'};
}
}
It's giving โจ self-modifying code, main character energy โจ. Periodt.
๐ RUNTIME INHERITANCE ๐
The class method ->add_class() lets you dynamically add classes to a class hierarchy at runtime. Like, while the program is already running. Wild, I know.
Say you had a cute little state class:
package Trait::State; {
use Object::InsideOut;
my %state :Field :Set(state);
}
You can graft that onto another class at runtime with:
My::Class->add_class('Trait::State');
So for example, application code can dynamically modify a class without it having to create an actual sub-class. No sub-classes were harmed in the making of this feature. ๐ฆ
๐ง PREPROCESSING ๐ง
๐ Parameter Preprocessing ๐
You can hand over a code ref โ either an anonymous subroutine or just a subroutine name, either works โ for an object initialization parameter, and it'll get called on that parameter before any of the other parameter actions we talked about earlier happen. Here's the vibe:
package My::Class; {
use Object::InsideOut;
# The parameter preprocessing subroutine
sub preproc
{
my ($class, $param, $spec, $obj, $value) = @_;
# Preform parameter preprocessing
...
# Return result
return ...;
}
my @data :Field
:Arg('Name' => 'DATA', 'Preprocess' => \&My::Class::preproc);
my %init_args :InitArgs = (
'PARAM' => {
'Preprocess' => \&preproc,
},
);
...
}
When you use it in the :Arg attribute, the subroutine name has to be fully-qualified, exactly like it's shown up there. And also โ if it's not referenced in the :InitArgs hash, the preprocessing subroutine can be given the :Private attribute.
As you can see from the example, the parameter preprocessing subroutine gets sent five arguments:
- The name of the class associated with the parameter
That'd be My::Class in the example above.
- The name of the parameter
Either DATA or PARAM in the example above.
- A hash ref of the parameter's specifiers
This is either a hash ref containing the :Arg attribute parameters, or the hash ref paired to the parameter's key in the :InitArgs hash.
- The object being initialized
- The parameter's value
This is the value assigned to the parameter in the ->new() method's argument list. If the parameter wasn't provided to ->new(), then undef will be sent.
Whatever the preprocessing subroutine returns gets assigned to the parameter. Slay.
โ ๏ธ Be careful about what types of data the preprocessing subroutine tries to make use of that's"external"to the arguments supplied. For instance, because the order of parameter processing is not specified, the preprocessing subroutine cannot rely on whether or not some other parameter is set. That kind of processing would need to be done in the:Initsubroutine. It can, however, make use of object data set by classes higher up in the class hierarchy. (That's literally why the object is provided as one of the arguments. ๐ซถ)
Possible uses for parameter preprocessing include:
- Overriding the supplied value (or even deleting it by returning
undef) - Providing a dynamically-determined default value
Oh and Preprocess may be abbreviated to Preproc or Pre. Short king behavior.
๐ Set Accessor Preprocessing ๐
You can also specify a code ref โ again, either an anonymous subroutine or a fully-qualified subroutine name โ for a set/combined accessor. It gets called on the arguments supplied to the accessor before the accessor does its usual type checking and adding-the-data-to-the-field thing. Behold:
package My::Class; {
use Object::InsideOut;
my @data :Field
:Acc('Name' => 'data', 'Preprocess' => \&My::Class::preproc);
# The set accessor preprocessing subroutine may be made 'Private'
sub preproc :Private
{
my ($self, $field, @args) = @_;
# Preform preprocessing on the accessor's arguments
...
# Return result
return ...;
}
}
As illustrated up there, the accessor preprocessing subroutine gets sent these arguments:
- The object used to invoke the accessor
- A reference to the field associated with the accessor
- The argument(s) sent to the accessor
There will always be at least one argument. Always. Non-negotiable.
Usually the preprocessing subroutine would just return a single value. But for fields declared as type List, multiple values may be returned. Multiple values!! โจ
After preprocessing, the set accessor will operate on whatever value(s) the preprocessing subroutine handed back.
๐ฅ SPECIAL PROCESSING ๐ฅ
๐ Object ID ๐
By default, an object's ID comes from a sequence counter for the object's class hierarchy. That should cover basically every case of class development, fr fr. But if there's a special need for the module code to control the object ID (check out Math::Random::MT::Auto as an example), then you can specify a subroutine labelled with the :ID attribute:
sub _id :ID
{
my $class = $_[0];
# Generate/determine a unique object ID
...
return ($id);
}
The ID your subroutine returns can be any kind of regular scalar (like a string or a number). However โ if the ID is something other than a low-valued integer, then you're gonna have to architect all your classes using hashes for the object fields. See "HASH ONLY CLASSES" for the details. ๐
And within any class hierarchy, only one class may specify an :ID subroutine. One. Uno. That's it.
๐ฆ Object Replication ๐ฆ
Object replication happens explicitly when you call the ->clone() method on an object, and implicitly when threads get created in a threaded application. In nearly all cases, Object::InsideOut handles all the details for you. We love a module that does the work.
In rare cases though, a class may need special handling for object replication. When that happens, it has to provide a subroutine labelled with the :Replicate attribute. That subroutine gets sent three arguments: the parent and the cloned objects, plus a flag:
sub _replicate :Replicate
{
my ($parent, $clone, $flag) = @_;
# Special object replication processing
if ($clone eq 'CLONE') {
# Handling for thread cloning
...
} elsif ($clone eq 'deep') {
# Deep copy of the parent
...
} else {
# Shallow copying
...
}
}
So here's the tea on the flag: in the case of thread cloning, $flag will be set to the 'CLONE', and the $parent object is just a non-blessed anonymous scalar reference that contains the ID for the object in the parent thread. iykyk
When it's invoked via the ->clone() method, $flag will be either an empty string โ which denotes that a shallow copy is being produced for the clone โ or $flag will be set to 'deep', which indicates a deep copy is being produced.
The :Replicate subroutine only needs to deal with the special replication processing that your object needs. 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 arrays (hashes). If a class needs extra destruction processing (like, closing filehandles and stuff), then it has to provide a subroutine labelled with the :Destroy attribute. That subroutine gets sent the object being destroyed:
sub _destroy :Destroy
{
my $obj = $_[0];
# Special object destruction processing
}
And the :Destroy subroutine only needs to deal with the special destruction processing โ the DESTROY method will handle all the other details of object destruction. Teamwork makes the dream work. ๐
โจ FOREIGN CLASS INHERITANCE โจ
okay bestie, this one's kind of a big deal ๐ Object::InsideOut is fully down to let you inherit from foreign classes โ aka non-Object::InsideOut classes. So like, your class can inherit from another Perl class and yoink its methods right into your own objects. iconic behavior fr.
One way to declare foreign class inheritance is just adding the class name to the Object::InsideOut declaration inside your package:
package My::Class; {
use Object::InsideOut qw(Foreign::Class);
...
}
That gets you access to the foreign class's static (i.e., class) methods from your own class ๐ซถ So like, say Foreign::Class has a class method called foo. With the above? You can hit that method with My::Class->foo() instead. slay.
Multiple foreign inheritance is supported too, obviously:
package My::Class; {
use Object::InsideOut qw(Foreign::Class Other::Foreign::Class);
...
}
$self->inherit($obj, ...)โ to use object methods from foreign classes, an object has to inherit from an object of that class. Normally you'd do this inside a class's:Initsubroutine:
package My::Class; {
use Object::InsideOut qw(Foreign::Class);
sub init :Init
{
my ($self, $args) = @_;
my $foreign_obj = Foreign::Class->new(...);
$self->inherit($foreign_obj);
}
}
So with all that, if Foreign::Class has an object method called bar, you can call it straight from your own objects:
package main;
my $obj = My::Class->new();
$obj->bar();
Object::InsideOut's AUTOLOAD subroutine handles the dispatching of the ->bar() method call using the internally held inherited object (in this case, $foreign_obj). no cap, it just does the thing.
Multiple inheritance is supported as well: you can call the ->inherit() method multiple times, or make just one call with all the objects to be inherited from. either way, chef's kiss.
->inherit() is a restricted method. Translation? You can't use it on an object outside of code belonging to the object's class tree (e.g., you can't call it from application code). keep it in the family bestie ๐
In the event of a method naming conflict, the ->inherit() method can be called using its fully-qualified name:
$self->Object::InsideOut::inherit($obj);
my @objs = $self->heritage();my $obj = $self->heritage($class);my @objs = $self->heritage($class1, $class2, ...);โ your class code can retrieve any inherited objects using the->heritage()method ๐ฆ When called without any arguments, it returns a list of any objects that were stored by the calling class using the calling object. In other words, if classMy::Classuses object$objto store foreign objects$fobj1and$fobj2, then later on in classMy::Class,$obj->heritage()will return$fobj1and$fobj2. it's giving ancestry.com.
->heritage() can also be called with one or more class name arguments. In that case, only objects of the specified class(es) are returned. filtered, curated, on brand.
In the event of a method naming conflict, the ->heritage() method can be called using its fully-qualified name:
my @objs = $self->Object::InsideOut::heritage();
$self->disinherit($class [, ...])$self->disinherit($obj [, ...])โ the->disinherit()method disassociates (i.e., deletes) the inheritance of foreign object(s) from an object. The foreign objects may be specified by class, or using the actual inherited object (retrieved via->heritage(), for example).
The call is only effective when called inside the class code that established the initial inheritance. In other words, if an inheritance is set up inside a class, then disinheritance can only be done from inside that class. it's a closed circle, sorry ๐
In the event of a method naming conflict, the ->disinherit() method can be called using its fully-qualified name:
$self->Object::InsideOut::disinherit($obj [, ...])
NOTE: With foreign inheritance, you only have access to class and object methods. The encapsulation of the inherited objects is strong, meaning that only the class where the inheritance takes place has direct access to the inherited object. If access to the inherited objects themselves, or their internal hash fields (in the case of blessed hash objects), is needed outside the class, then you'll need to write your own accessors for that. build your own door if you want in ๐ช
LIMITATION: You cannot use fully-qualified method names to access foreign methods (when encapsulated foreign objects are involved). So like, the following will not work:
my $obj = My::Class->new();
$obj->Foreign::Class::bar();
Normally, you shouldn't ever need to do the above: $obj->bar() would suffice. lowkey just use the short one.
The only time this may be an issue is when the native class overrides an inherited foreign class's method (e.g., My::Class has its own ->bar() method). Such overridden methods are not directly callable. If such overriding is intentional, then this should not be an issue: no one should be writing code that tries to by-pass the override. However, if the overriding is accidentally, then either the native method should be renamed, or the native class should provide a wrapper method so that the functionality of the overridden method is made available under a different name. messy but fixable ๐ธ
๐ "use base" and Fully-qualified Method Names ๐
The foreign inheritance methodology handled by the above is predicated on non-Object::InsideOut classes that generate their own objects and expect their object methods to be invoked via those objects. very "i made myself, i run myself" energy.
There are exceptions to this rule:
- Foreign object methods that expect to be invoked via the inheriting class's object, or foreign object methods that don't care how they are invoked (i.e., they don't make reference to the invoking object).
This is the case where a class provides auxiliary methods for your objects, but from which you don't actually create any objects (i.e., there is no corresponding foreign object, and $obj->inherit($foreign) is not used.)
In this case, you can either:
- Declare the foreign class using the standard method (i.e.,
use Object::InsideOut qw(Foreign::Class);), and invoke its methods using their full path (e.g.,$obj->Foreign::Class::method();); or - You can use the base pragma so that you don't have to use the full path for foreign methods.
package My::Class; {
use Object::InsideOut;
use base 'Foreign::Class';
...
}
The former scheme is faster. โก
- Foreign class methods that expect to be invoked via the inheriting class.
As with the above, you can either invoke the class methods using their full path (e.g., My::Class->Foreign::Class::method();), or you can use base so that you don't have to use the full path. Again, using the full path is faster.
Class::Singleton is an example of this type of class.
- Class methods that don't care how they are invoked (i.e., they don't make reference to the invoking class).
In this case, you can either use use Object::InsideOut qw(Foreign::Class); for consistency, or use use base qw(Foreign::Class); if (slightly) better performance is needed.
If you're not familiar with the inner workings of the foreign class such that you don't know if or which of the above exceptions applies, then the formulaic approach would be to first use the documented method for foreign inheritance (i.e., use Object::InsideOut qw(Foreign::Class);). If that works, then I strongly recommend that you just use that approach unless you have a good reason not to. If it doesn't work, then try use base. trust the process bestie, it'll tell you what it wants ๐
๐ฎ INTROSPECTION ๐ฎ
For Perl 5.8.0 and later, Object::InsideOut provides an introspection API that allow you to obtain metadata on a class's hierarchy, constructor parameters, and methods. look inside the class, see the whole vibe ๐ง
my $meta = My::Class->meta();my $meta = $obj->meta();โ the->meta()method, which is exported by Object::InsideOut to each class, returns anObject::InsideOut::Metadataobject which can then be queried for information about the invoking class or invoking object's class:
# Get an object's class hierarchy
my @classes = $obj->meta()->get_classes();
# Get info on the args for a class's constructor (i.e., ->new() parameters)
my %args = My::Class->meta()->get_args();
# Get info on the methods that can be called by an object
my %methods = $obj->meta()->get_methods();
My::Class->isa();$obj->isa();โ when called in an array context, calling->isa()without any arguments on an Object::InsideOut class or object returns a list of the classes in the class hierarchy for that class or object, and is equivalent to:
my @classes = $obj->meta()->get_classes();
When called in a scalar context, it returns an array ref containing the classes.
My::Class->can();$obj->can();โ when called in an array context, calling->can()without any arguments on an Object::InsideOut class or object returns a list of the method names for that class or object, and is equivalent to:
my %methods = $obj->meta()->get_methods();
my @methods = keys(%methods);
When called in a scalar context, it returns an array ref containing the method names.
See Object::InsideOut::Metadata for more details. ๐ซถ
๐งต THREAD SUPPORT ๐งต
Okay bestie, gather round, because this part is giving concurrency energy. ๐
For Perl 5.8.1 and later, Object::InsideOut fully supports threads (i.e., it is thread safe โจ), and it 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. (Important: using require threads; after the program is already running is not supported ๐.) And if object sharing is gonna be a thing, then use threads::shared; should follow.
If you just use threads; all on its own, then objects from one thread get copied and made available in a child thread. Simple as that, no cap.
Now โ the addition of use threads::shared; by itself does not alter the behavior of Object::InsideOut objects. The default behavior is to not share objects between threads (i.e., they act exactly the same as with use threads; alone). So don't expect magic just because you typed it. ๐
To actually enable the sharing of objects between threads, you have to specify which classes will be involved with thread object sharing. There are two methods for doing this. The first one involves setting a ::shared variable (inside a BEGIN block) for the class prior to its use:
use threads;
use threads::shared;
BEGIN {
$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';
...
}
And here's the vibe: when either sharing flag is set for one class in an object hierarchy, then all the classes in the hierarchy are affected. The whole squad gets pulled in, periodt. ๐
But if a class cannot support thread object sharing (e.g., one of the object fields contains code refs โ which Perl literally cannot share between threads, no shade to Perl), it should specifically declare this fact:
package My::Class; {
use Object::InsideOut ':NOT_SHARED';
...
}
However โ and this is the part where it gets messy ๐ญ โ you cannot mix 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!
...
}
See that # ERROR!? Yeah. It's an error. Don't do it. ๐ซ
Here's a complete example with thread object sharing enabled, and it's honestly kind of iconic:
use threads;
use threads::shared;
package My::Class; {
use Object::InsideOut ':SHARED';
# One list-type field
my @data :Field :Type(list) :Acc(data);
}
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 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"
Look at that last line โ the child thread changed the data and the parent thread saw it. That's the sharing. That's the whole point. ๐ฆ
#๏ธโฃ HASH ONLY CLASSES #๏ธโฃ
Okay so for performance considerations, it is recommended that arrays be used for class fields whenever possible. Just trust the process. โก
The only time when hash-based fields are required is when a class must provide its own object ID, and those IDs are something other than low-valued integers. In this case, hashes must be used for fields not only in the class that defines the object ID subroutine, but also in every class in any class hierarchy that includes such a class. Yeah, the whole tree. ๐ณ
The hash-only requirement can be enforced by adding the :HASH_ONLY flag to a class's use Object::InsideOut ... declaration:
package My::Class; {
use Object::InsideOut ':hash_only';
...
}
This will cause Object::Inside to check every class in any class hierarchy that includes such flagged classes to make sure their fields are hashes and not arrays. It will also fail any ->create_field() call that tries to create an array-based field in any such class. So, like, don't try it. ๐
๐ SECURITY ๐
Bestie. Sit down. ๐ซ
In the default case where Object::InsideOut provides object IDs that are sequential integers, it is possible to hack together a fake Object::InsideOut object, and so gain access to another object's data:
my $fake = bless(\do{my $scalar}, 'Some::Class');
$$fake = 86; # ID of another object
my $stolen = $fake->get_data();
Yeah. You read that right. $stolen. Stealing. ๐ญ
Now, why anyone would try to do this is unknown. How this could be used for any sort of malicious exploitation is also unknown. But if preventing this sort of security issue is a requirement, it can be accomplished by adding the :SECURE flag to a class's use Object::InsideOut ... declaration:
package My::Class; {
use Object::InsideOut ':SECURE';
...
}
This places the module Object::InsideOut::Secure in the class hierarchy. Object::InsideOut::Secure provides an :ID subroutine that generates random integers for object IDs, thus preventing other code from being able to create fake objects by guessing at IDs. So now the guesses go nowhere. Slay. ๐
Using:SECUREmode requiresMath::Random::MT::Auto(v5.04 or later). Don't @ me, that's just the requirement. ๐
Because the object IDs used with :SECURE mode are large random values, the :HASH_ONLY flag is forced on all the classes in the hierarchy. Automatic. No opting out. ๐
And for efficiency, it is recommended that the :SECURE flag be added to the topmost class(es) in a hierarchy. Top of the tree, queen behavior. ๐
๐ ATTRIBUTE HANDLERS ๐
Object::InsideOut uses attribute 'modify' handlers as described in "Package-specific Attribute Handling" in attributes, and provides a mechanism for adding attribute handlers to your own classes. Instead of naming your attribute handler as MODIFY_*_ATTRIBUTES, name it something else and then label it with the :MODIFY_*_ATTRIBUTES attribute (or :MOD_*_ATTRS for short). Your handler should work just as described in "Package-specific Attribute Handling" in attributes with regard to its input arguments, and must return a list of the attributes which were not recognized by your handler. Here's an example, and it's a whole mood:
package My::Class; {
use Object::InsideOut;
sub _scalar_attrs :MOD_SCALAR_ATTRS
{
my ($pkg, $scalar, @attrs) = @_;
my @unused_attrs; # List of any unhandled attributes
while (my $attr = shift(@attrs)) {
if ($attr =~ /.../) {
# Handle attribute
...
} else {
# We don't handle this attribute
push(@unused_attrs, $attr);
}
}
return (@unused_attrs); # Pass along unhandled attributes
}
}
Attribute 'modify' handlers are called upward through the class hierarchy (i.e., bottom up). This provides child classes with the capability to override the handling of attributes by parent classes, or to add attributes (via the returned list of unhandled attributes) for parent classes to process. Passing the baton up the chain. ๐โโ๏ธโจ
Attribute 'modify' handlers should be located at the beginning of a package, or at least before any use of attributes on the corresponding type of variable or subroutine:
package My::Class; {
use Object::InsideOut;
sub _array_attrs :MOD_ARRAY_ATTRS
{
...
}
my @my_array :MyArrayAttr;
}
Order matters, okay? Don't put it at the bottom and then be confused. ๐ค
For attribute 'fetch' handlers, follow the same procedures: Label the subroutine with the :FETCH_*_ATTRIBUTES attribute (or :FETCH_*_ATTRS for short). And here's a fun little twist โ contrary to the documentation in "Package-specific Attribute Handling" in attributes, attribute 'fetch' handlers receive two arguments: The relevant package name, and a reference to a variable or subroutine for which package-defined attributes are desired. So, like, the docs say one thing but this is the actual deal. ๐
Attribute handlers are normal rendered hidden. (Yep, that's exactly what it says. Mysterious. Iconic. ๐)
โจ SPECIAL USAGE โจ
๐ฆ Usage With Exporter
It is possible to use Exporter to export functions from one inside-out object class to another:
use strict;
use warnings;
package Foo; {
use Object::InsideOut 'Exporter';
BEGIN {
our @EXPORT_OK = qw(foo_name);
}
sub foo_name
{
return (__PACKAGE__);
}
}
package Bar; {
use Object::InsideOut 'Foo' => [ qw(foo_name) ];
sub get_foo_name
{
return (foo_name());
}
}
package main;
print("Bar got Foo's name as '", Bar::get_foo_name(), "'\n");
And note โ note this!! โ that the BEGIN block is needed to ensure that the Exporter symbol arrays (in this case @EXPORT_OK) get populated properly. Without it, things get weird. With it, we vibe. ๐ธ
๐ ๏ธ Usage With require and mod_perl
Object::InsideOut usage under mod_perl and with runtime-loaded classes is supported automatically; no special coding is required. Love that for us. ๐ซถ
Caveat: Runtime loading of classes should be performed before any objects are created within any of the classes in their hierarchies. If Object::InsideOut cannot create a hierarchy because of previously created objects (even if all those objects have been destroyed), a runtime error will be generated. So, order of operations, bestie. Don't skip steps. ๐ญ
๐ Singleton Classes
A singleton class is a case where you would provide your own ->new() method that in turn calls Object::InsideOut's ->new() method:
package My::Class; {
use Object::InsideOut;
my $singleton;
sub new {
my $thing = shift;
if (! $singleton) {
$singleton = $thing->Object::InsideOut::new(@_);
}
return ($singleton);
}
}
One object. One class. Main character energy only, no extras allowed. ๐๐
And that's part 11, but we're not done yet โ keep scrolling, bestie, there's more coming ๐
๐ DIAGNOSTICS ๐
okay bestie, when things go wrong, Object::InsideOut is not gonna leave you hanging โ it uses Exception::Class for reporting errors, and the base error class for this whole module is OIO. Here's the basic vibe for catching and handling stuff:
my $obj;
eval { $obj = My::Class->new(); };
if (my $e = OIO->caught()) {
warn('Failure creating object: '.$e);
...
}
and if you wanna go full main character and catch everything, you can layer it like this:
eval { ... };
if (my $e = OIO->caught()) {
# An error generated by Object::InsideOut
...
} elsif (my $e = Exception::Class::Base->caught()) {
# An error generated by other code that uses Exception::Class
...
} elsif ($@) {
# An unhandled error (i.e., generated by code that doesn't use
# Exception::Class)
...
}
the author literally tried to make the messages and info coming back from the error objects as informative as possible, and suggestions for improvements are welcome ๐ซถ. Also โ if you ever hit a situation where an error pops off as a result of Object::InsideOut code that doesn't generate an Exception::Class object, please bring it to their attention. Here's one of those:
- Invalid ARRAY/HASH attribute โ this error is giving "you forgot
use Object::InsideOut;in your class's code." that's it, that's the whole thing ๐
the __DIE__ handler situation (iykyk)
Object::InsideOut installs a __DIE__ handler (peep die LIST in perlfunc and eval BLOCK in perlfunc) so it can catch any errant exceptions flying out of class-specific code โ specifically the :Init, :Replicate, :Destroy, etc. subroutines. so when you're using eval blocks inside those subroutines, you should localize $SIG{'__DIE__'} so Object::InsideOut's handler doesn't interfere with exceptions generated inside your eval blocks. like:
sub _init :Init {
...
eval {
local $SIG{'__DIE__'};
...
};
if $@ {
# Handle caught exception
}
...
}
here's another one, where die is being used as a method of flow control to bounce out of an eval block:
eval {
local $SIG{'__DIE__'}; # Suppress any existing __DIE__ handler
...
die({'found' => 1}) if $found; # Leave the eval block
...
};
if ($@) {
die unless (ref($@) && $@->{'found'}); # Propagate any 'real' error
# Handle 'found' case
...
}
# Handle 'not found' case
and same energy: if you're calling code from other modules that use that flow control mechanism but don't localize $SIG{'__DIE__'}, you can workaround that deficiency with your own eval block:
eval {
local $SIG{'__DIE__'}; # Suppress any existing __DIE__ handler
Some::Module::func(); # Call function that fails to localize
};
if ($@) {
# Handle caught exception
}
also pls file a bug report against the offending module, with a patch that adds the missing local $SIG{'__DIE__'}; statement. do it for the girlies ๐
๐ธ BUGS AND LIMITATIONS ๐ธ
okay so. if you get an error that looks like this:
ERROR: Attempt to DESTROY object ID 1 of class Foo twice
the cause may be that some module your app uses is doing require threads somewhere in the background. DBI is one such module ๐. the workaround is to add use threads; at the start of your application.
another cause of the above is returning a non-shared object from a thread โ either explicitly, or implicitly when the result of the last statement in the thread subroutine is an object. like:
sub thr_func {
my $obj = MyClass->new();
}
which is equivalent to:
sub thr_func {
return MyClass->new();
}
you can dodge this by making sure your thread subroutine ends with return;.
the equality operator (e.g., if ($obj1 == $obj2) { ...) is overloaded for :SHARED classes when threads::shared is loaded. the overload subroutine compares object classes and IDs, because references to the same thread shared object may have different refaddrs. (sneaky!!)
you cannot overload an object to a scalar context (i.e., you can't :SCALARIFY). nope. not happening.
you cannot use two instances of the same class with mixed thread object sharing in same application.
you cannot use attributes on subroutine stubs (i.e., forward declaration without later definition) with :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, the entirety of any one attribute must be on a single line. (however โ multiple attributes may appear on separate lines. so like, there's that ๐ซ)
if a set accessor accepts scalars, then you can store any inside-out object type in it. if its Type is set to HASH, then it can store any blessed hash object.
returning objects from threads does not work:
my $obj = threads->create(sub { return (Foo->new()); })->join(); # BAD
instead, use thread object sharing โ create the object before launching the thread, and then manipulate the object inside the thread:
my $obj = Foo->new(); # Class 'Foo' is set ':SHARED'
threads->create(sub { $obj->set_data('bar'); })->join();
my $data = $obj->get_data();
due to a limitation in threads::shared version 1.39 and earlier, if you're storing shared objects inside other shared objects, you should use delete() to remove them from internal fields (e.g., delete($field[$$self]);) when necessary, so that the objects' destructor gets called. upgrading to version 1.40 or later alleviates most of this issue except during global destruction. see threads::shared for more.
with Perl 5.8.8 and earlier, there are bugs associated with threads::shared that may prevent you from storing objects inside of shared objects, or using foreign inheritance with shared objects. with Perl 5.8.9 (and later) together with threads::shared 1.15 (and later), you can store shared objects inside of other shared objects, and you can use foreign inheritance with shared objects (provided the foreign class supports shared objects as well). glow up โจ
due to internal complexities, the following actions are not supported in code that uses threads::shared while there are any threads active:
- Runtime loading of Object::InsideOut classes
- Using
->add_class()
it's recommended that such activities, if needed, be performed in the main application code before any threads are created (or at least while there are no active threads).
for Perl 5.6.0 through 5.8.0, a Perl bug prevents package variables (e.g., object attribute arrays/hashes) from being referenced properly from subroutine refs returned by an :Automethod subroutine. for Perl 5.8.0 there is no workaround: this bug causes Perl to core dump ๐. for Perl 5.6.0 through 5.6.2, the workaround is to create a ref to the required variable inside the :Automethod subroutine, and use that inside the subroutine ref:
package My::Class; {
use Object::InsideOut;
my %data;
sub auto :Automethod
{
my $self = $_[0];
my $name = $_;
my $data = \%data; # Workaround for 5.6.X bug
return sub {
my $self = shift;
if (! @_) {
return ($$data{$name});
}
$$data{$name} = shift;
};
}
}
for Perl 5.8.1 through 5.8.4, a Perl bug produces spurious warning messages when threads are destroyed. these messages are innocuous, and can be suppressed by adding the following to your application code:
$SIG{'__WARN__'} = sub {
if ($_[0] !~ /^Attempt to free unreferenced scalar/) {
print(STDERR @_);
}
};
a better solution would be to upgrade threads and threads::shared from CPAN, especially if you encounter other problems associated with threads.
for Perl 5.8.4 and 5.8.5, the Storable feature does not work due to a Perl bug. use Object::InsideOut v1.33 if needed.
due to bugs in the Perl interpreter, using the introspection API (i.e. ->meta(), etc.) requires Perl 5.8.0 or later.
the version of Want that is available via PPM for ActivePerl is defective, and causes failures when using :lvalue accessors. remove it, and then download and install the Want module using CPAN.
Devel::StackTrace (used by Exception::Class) makes use of the DB namespace. as a consequence, Object::InsideOut thinks that package DB is already loaded. therefore, if you create a class called DB that is sub-classed by other packages, you may need to require it as follows:
package DB::Sub; {
require DB;
use Object::InsideOut qw(DB);
...
}
view existing bug reports at, and submit any new bugs, problems, patches, etc. to:
<http://rt.cpan.org/Public/Dist/Display.html?Name=Object-InsideOut>
โญ REQUIREMENTS โญ
the whole shopping list, bestie:
- Perl 5.6.0 or later
- Exception::Class v1.22 or later
- Scalar::Util v1.10 or later
- it is possible to install a pure perl version of Scalar::Util, however, it will be missing the
weaken()function which is needed byObject::InsideOut. you'll need to upgrade your version ofScalar::Utilto one that supports itsXScode.
- it is possible to install a pure perl version of Scalar::Util, however, it will be missing the
- Test::More v0.50 or later
- needed for testing during installation.
- Want v0.12 or later
- optional. provides support for
:lvalue Accessors.
- optional. provides support for
- Math::Random::MT::Auto v5.04 or later)
- optional. provides support for
:SECUREmode.
- optional. provides support for
to cover all of the above requirements and more, it's recommended that you install Bundle::Object::InsideOut using CPAN:
perl -MCPAN -e 'install Bundle::Object::InsideOut'
this will install the latest versions of all the required and optional modules needed for full support of all of the features provided by Object::InsideOut. one command, whole bundle, slay ๐
๐ฆ SEE ALSO ๐ฆ
- Object::InsideOut on MetaCPAN:
<https://metacpan.org/release/Object-InsideOut> - code repository:
<https://github.com/jdhedden/Object-InsideOut> - Inside-out Object Model:
<http://www.perlfoundation.org/perl5/index.cgi?inside_out_object>,<http://www.perlmonks.org/?node_id=219378>,<http://www.perlmonks.org/?node_id=483162>,<http://www.perlmonks.org/?node_id=515650>, Chapters 15 and 16 of Perl Best Practices by Damian Conway - Object::InsideOut::Metadata
- Storable,
<Exception:Class>, Want, Math::Random::MT::Auto, attributes, overload - sample code in the examples directory of this distribution on CPAN.
๐ ACKNOWLEDGEMENTS ๐
okay so real talk, this module did NOT build itself, and bestie we GOTTA shout out the icons who made it happen:
- Abigail
<perl AT abigail DOT nl>โ for inside-out objects in general. like, the whole entire concept? that was her. the blueprint. the moment. ๐ - Damian Conway
<dconway AT cpan DOT org>โ forClass::Std, and for delegator methods. giving us delegation so we don't have to write it ourselves, slay. ๐ - David A. Golden
<dagolden AT cpan DOT org>โ for thread handling for inside-out objects. threading is lowkey terrifying and he just... handled it. ๐ญ๐ซถ - Dan Kubb
<dan.kubb-cpan AT autopilotmarketing DOT com>โ for":Chained"methods. the chain? he built the chain. iconic behavior fr fr. ๐โจ
๐ AUTHOR ๐
Jerry D. Hedden, <jdhedden AT cpan DOT org>
that's the main character right there. the whole page, the whole module, the whole vibe. ๐
๐ COPYRIGHT AND LICENSE ๐
Copyright 2005 - 2012 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.
translation bestie: it's free, it's open, you can share it and remix it, you just gotta keep it under the same terms as Perl itself. no cap, that's the deal. ๐
๐ TRANSLATIONS ๐
A Japanese translation of this documentation by TSUJII, Naofumi <tsun DOT nt AT gmail DOT com> is available at <http://perldoc.jp/docs/modules/>.
so if Japanese is your language, you are SO covered. go off. ๐ธโจ
and that's the whole page bestie!! you made it to the end, which is honestly a flex. go write some inside-out objects, keep your data private, and remember: your fields are nobody's business but your own. periodt. ๐ ๐
๐ฆ this was perl v5.36.0, dated 2022-11-29, and yes we're still calling it Object...sideOut(3pm) because that's its name and we respect it ๐ฆ
okay bye ilysm, go slay some Perl ๐๐
๐ฆ where this page comes from
- debian bookworm: libobject-insideout-perl 4.05-2 ยท Artistic-1.0-Perl OR GPL-1.0-or-later
- debian trixie: libobject-insideout-perl 4.05-2 ยท Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu noble: libobject-insideout-perl 4.05-2 ยท Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu resolute: libobject-insideout-perl 4.05-2 ยท Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu stonking: libobject-insideout-perl 4.05-2 ยท Artistic-1.0-Perl OR GPL-1.0-or-later
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms