Class:...totyped(3pm)

🌸 NAME 🌸

Class::Prototyped β€” fast prototype-based OO programming in Perl πŸ’…βœ¨

(also hiiii, this is the Class:...totyped(3pm) page from the User Contributed Perl Documentation, and yes we are doing the whole thing, grab a snack bestie πŸ“)


✨ SYNOPSIS ✨

okay so here's the entire vibe in one shot, no cap:

    use strict;
    use Class::Prototyped ':EZACCESS';

    $, = ' '; $\ = "\n";

    my $p = Class::Prototyped->new(
      field1 => 123,
      sub1   => sub { print "this is sub1 in p" },
      sub2   => sub { print "this is sub2 in p" }
    );

    $p->sub1;
    print $p->field1;
    $p->field1('something new');
    print $p->field1;

    my $p2 = Class::Prototyped->new(
      'parent*' => $p,
      field2    => 234,
      sub2      => sub { print "this is sub2 in p2" }
    );

    $p2->sub1;
    $p2->sub2;
    print ref($p2), $p2->field1, $p2->field2;
    $p2->field1('and now for something different');
    print ref($p2), $p2->field1;

    $p2->addSlots( sub1 => sub { print "this is sub1 in p2" } );
    $p2->sub1;

    print ref($p2), "has slots", $p2->reflect->slotNames;

    $p2->reflect->include( 'xx.pl' ); # includes xx.pl in $p2's package
    print ref($p2), "has slots", $p2->reflect->slotNames;
    $p2->aa();    # calls aa from included file xx.pl

    $p2->deleteSlots('sub1');
    $p2->sub1;

πŸ’– DESCRIPTION πŸ’–

This package is literally the way to do efficient and simple prototype-based programming in Perl πŸ¦‹. You can hand different subroutines to different objects, and objects can also inherit their behavior and state from another object. It's giving inheritance but make it ✨personal✨.

The structure of an object gets inspected and modified through mirrors πŸͺž, which you create by calling reflect on an object or on a class that inherits from Class::Prototyped.


πŸ›  Installation instructions πŸ› 

This module needs Module::Build 0.24 for the automated installation procedures. With Module::Build installed, you go:

Build.PL
perl build test
perl build install

It can also be installed under ActivePerl for Win32 by downloading the PPM from CPAN β€” the file has the extension .ppm.zip. To install it, download the .ppm.zip file, uncompress it, and run:

ppm install Class-Prototyped.ppd

And if you're the manual type (respect 😌), you can install it by copying lib/Class/Prototyped.pm to perl/site/lib/Class/Prototyped.pm β€” along with Graph.pm if you want it.


πŸ‘‘ WHEN TO USE THIS MODULE πŸ‘‘

When I reach for Class::Prototyped, it's generally because I really need it. When the cleanest way to solve a problem is for the code that uses a module to subclass from it, that is generally a sign that Class::Prototyped would be of use. If you catch yourself dodging the whole problem by passing anonymous subroutines as parameters to the new method, that's another good sign that you should be using prototype based programming πŸ‘€. If you find yourself storing anonymous subroutines in databases, configuration files, or text files, and then writing infrastructure to handle calling those anonymous subroutines, that's yet another sign πŸ’€. And when you expect the people using your module to want to change the behavior, override subroutines, and so forth β€” yeah bestie, that's a sign.


🧠 CONCEPTS 🧠

πŸŽ€ Slots πŸŽ€

Class::Prototyped borrows very strongly from the language Self (peep http://www.sun.com/research/self for more info, iykyk). The core concept in Self is the concept of a slot. Think of slots as entries in a hash, except that instead of just pointing to data, they can point to objects, code, or parent objects. Main character energy for a hash entry, fr fr.

So what happens when you send a message to an object (aka you make a method call on the object)? First, Perl looks for that slot in the object. If it can't find that slot in the object, it searches for that slot in one of the object's parents (we'll circle back to that). Once it finds the slot: if the slot is a block of code, it evaluates the code and returns the return value. If the slot references data, it returns that data. And if you assign to a data slot (through a method call), it modifies the data. Simple as that πŸ’….

Distinguishing data slots from method slots is easy β€” the latter are references to code blocks, the former are not. Distinguishing parent slots is not so easy, so instead a simple naming convention is used: if the name of the slot ends in an asterisk, the slot is a parent slot. If you have programmed in Self, this naming convention will feel very familiar 🌸.

πŸͺž Reflecting πŸͺž

In Self, to examine the structure of an object, you use a mirror. Just like using his shield as a mirror let Perseus slay Medusa, holding up a mirror lets us look upon an object's structure without any name space collisions ✨.

Once you have a mirror, you can add and delete slots like so:

    my $cp = Class::Prototyped->new();
    my $mirror = $cp->reflect();
    $mirror->addSlots(
      field1 => 'foo',
      sub1   => sub {
        print "this is sub1 printing field1: '".$_[0]->field1."'\n";
      },
    );

    $mirror->deleteSlot('sub1');

In addition, there is a more verbose syntax for addSlots where the slot name is replaced by an anonymous array β€” this is most commonly used to control the slot attributes.

    $cp->reflect->addSlot(
      [qw(field1 FIELD)] => 'foo',
      [qw(sub1 METHOD)]  => sub { print "hi there.\n"; },
    );

Because the mirror methods super, addSlot(s), deleteSlot(s), and getSlot(s) get called constantly on objects, there's an import keyword :EZACCESS that adds methods to the object space which call the appropriate reflected variants. Convenience queen πŸ‘‘.

🌈 Slot Attributes 🌈

Slot attributes let you specify additional information and behavior relating to a specific slot in an extensible manner. For instance, you might want to mark a specific field slot as constant, or attach a description to a given slot.

Slot attributes are divided up in two ways. The first is by the type of slot β€” FIELD, METHOD, or PARENT. Some slot attributes apply to all three, some to just two, and some to only one. The second division is on the type of slot attribute:

  • implementor β€” these are responsible for implementing the behavior of a slot. An example is a FIELD slot with the attribute constant. A slot is only allowed one implementor. All slot types have a default implementor. For FIELD slots, it is a read-write scalar. For METHOD slots, it is the passed anonymous subroutine. For PARENT slots, implementor and filter slot attributes don't really make sense 🀷.
  • filter β€” these filter access to the implementor. The quintessential example is the profile attribute. When set, this increments a counter in $Class::Prototyped::Mirror::PROFILE::counts every time the underlying FIELD or METHOD is accessed. Filter attributes can be stacked, so each attribute is assigned a rank, with lower values being closer to the implementor and higher values being closer to the caller.
  • advisory β€” these slot attributes serve one of two purposes. They can be used to store information about the slot (i.e. description attributes), and they can be used to pass information to the addSlots method (i.e. the promote attribute, which can be used to promote a new PARENT slot ahead of all the existing PARENT slots).

There is currently no formal interface for creating your own attributes β€” if you feel the need for new attributes, please contact the maintainer first to see if it might make sense to add the new attribute to Class::Prototyped. If not, the contact might provide enough impetus to define a formal interface. The attributes are currently defined in $Class::Prototyped::Mirror::attributes.

Finally, see the defaultAttributes method for information about setting default attributes. This can be used, for instance, to turn on profiling everywhere πŸ‘€.

πŸ¦‹ Classes vs. Objects πŸ¦‹

In Self, everything is an object and there are no classes at all. Perl, for better or worse, has a class system based on packages. We decided it would be better not to throw out the conventional way of structuring inheritance hierarchies, so in Class::Prototyped, classes are first-class objects.

However, objects are not first-class classes. To understand this dichotomy, we need to understand that there's a difference between the way "classes" and the way "objects" are expected to behave. The central difference is that "classes" are expected to persist whether or not there are any references to them. If you create a class, the class exists whether or not it appears in anyone's @ISA and whether or not there are any objects in it. Once a class is created, it persists until the program terminates. Iconic, honestly.

Objects, on the other hand, should follow the normal behaviors of reference-counted destruction β€” once the number of references to them drops to zero, they should miraculously disappear: the memory they used needs to be returned to Perl, their DESTROY methods need to be called, and so forth πŸ’€.

Since we don't require this behavior of classes, it's easy to have a way to get from a package name to an object β€” we simply stash the object that implements the class in $Class::Prototyped::Mirror::objects{$package}. But we can't do this for objects, because if we did, the object would persist forever, because that reference would always exist. 😭

Weak references would solve this problem, but weak references are still considered alpha and unsupported ($WeakRef::VERSION = 0.01), and we didn't want to make Class::Prototyped dependent on such a module.

So instead, we differentiate between classes and objects. In a nutshell: if an object has an explicit package name (i.e. something other than the auto-generated one), it is considered to be a class, which means it persists even if the object goes out of scope.

To create such an object, use the newPackage method, like so (the encapsulating block exists solely to demonstrate that classes are not scoped):

    {
      my $object = Class::Prototyped->newPackage('MyClass',
          field => 1,
          double => sub {$_[0]->field*2}
        );
    }

    print MyClass->double,"\n";

Notice that the class persists even though $object goes out of scope. If $object were created with an auto-generated package, that would not be true. So, for instance, it would be a very, very, very bad idea to add the package name of an object as a parent to another object 🚨 β€” when the first object goes out of scope, the package will disappear, but the second object will still have it in it's @ISA.

Except for the crucial difference that you should never, ever, ever make use of the package name for an object for any purpose other than printing it to the screen, objects and classes are simply different ways of inspecting the same entity 🫢.

okay so going from an object to a package? bestie you have two options and they're literally the same thing:

$package = ref($object);
$package = $object->reflect->package;

Same outcome, no cap, except the first one is wayyyyy faster. But listen: if $object lives in an auto-generated package, do not do anything with that $package except print it. that's it. that's the whole vibe. 🫢

Going the other direction, package ➑️ object:

$object = $package->reflect->object;

and lowkey $package is just the package name, so this absolutely works:

$object = MyClass->reflect->object;

Just remember $package has to be an actual class, not some auto-generated package name for an object. πŸ’…

🌸 Class Manipulation 🌸

okay this is where it gets fun β€” we can manipulate classes at runtime and it's giving main character energy. wanna add a new method to MyClass on the fly? say MyClass inherits from Class::Prototyped, or you passed :REFLECT on the use Class::Prototyped line β€” then you literally just:

MyClass->reflect->addSlot(myMethod => sub {print "Hi there\n"});

and if you're working with a class that does not inherit from Class::Prototyped and you don't wanna pass :REFLECT (which, btw, adds reflect to the UNIVERSAL package β€” kinda invasive ngl), you can do it like this instead:

my $mirror = Class::Prototyped::Mirror->new('MyClass');
$mirror->addSlot(myMethod => sub {print "Hi there\n"});

Also!! just like you can clone objects, you can clone classes derived from Class::Prototyped β€” you get a brand new object holding a copy of every slot that was defined in the class. ✨ but if all you want is to run Data::Dumper on a class, the preferred move is MyClass->reflect->object. and honestly even easier? use the dump mirror method. πŸŽ€

The code that implements reflection on classes automatically creates slot names for package methods and parent slots for everything in @ISA. which means you can just code classes the normal way β€” do your inheritance in @ISA, write your package methods, and it all works. iconic behavior honestly.

Now, if you manually add subroutines to a package at runtime and you want the slot info updated properly (okay yes, this really should be done through the addSlots mechanism, but maybe you're twisted 😏), you do something like:

$package->reflect->_vivified_methods(0);
$package->reflect->_autovivify_methods;

πŸ¦‹ Parent Slots πŸ¦‹

Adding parent slots is literally no different from adding normal slots β€” the naming scheme handles the differentiation for you. the way that it just… works. 🧚

So to add $foo as a parent to $bar:

$bar->reflect->addSlot('fooParent*' => $foo);

But since we treat classes as first-class objects around here, you can also write:

$bar->reflect->addSlot('mixIn*' => 'MyMix::Class');

It'll automatically require the module in the namespace of $bar and make that module a parent of the object. yes, it can load a module from disk if it needs to. girlboss. πŸ‘‘

Feeling lazy? add parents with no name at all:

$bar->reflect->addSlot('*' => $foo);

The slots get named automatically for the package you passed in β€” and for Class::Prototyped objects, that package looks like PKG0x12345678. so check this out, in the example below the parent slot ends up named MyMix::Class*:

$bar->reflect->addSlot('*' => 'MyMix::Class');

Parent slots get added to the inheritance hierarchy in the order you added them. meaning in this code, slots that aren't in $foo get looked up in $fred (and all of $fred's parent slots) before anything checks $jill:

$foo->reflect->addSlots('fred*' => $fred, 'jill*' => $jill);

also fun fact, addSlot and addSlots are identical β€” the variants only exist because calling addSlots to add a single slot is kinda ugly. that's it. that's the reason. πŸ’€

Need to reorder parent slots on an object? go look at promoteParents. that said, there's a shortcut for prepending a slot to the inheritance hierarchy β€” just define promote as a slot attribute using the extended slot syntax. and finally, keeping with our whole classes-are-first-class-objects thing, you can modify a class's inheritance hierarchy with addSlots and deleteSlots, exact same as objects. this next bit adds the $foo object as a parent of the MyClass class and prepends it to the hierarchy:

MyClass->reflect->addSlots([qw(foo* promote)] => $foo);

πŸ’œ Operator Overloading πŸ’œ

In Class::Prototyped, operator overloading = you add slots with the right name. first things first: when you use Class::Prototyped, make sure you pass :OVERLOAD so the operator overloading support is actually enabled. don't skip it bestie. πŸ™ˆ

Then you just pass the desired methods in as part of object creation, like so:

$foo = Class::Prototyped->new(
    value => 3,
    '""'  => sub { my $self = shift; $self->value( $self->value + 1 ) },
);

This creates an object that increments its value field by one and returns that incremented value every single time it gets stringified. obsessed. ✨

Now the tricky bit: there's no way to find out which operators are overloaded. so if you add overloading to a class with use overload, that behavior will not show up as slots when you reflect on the class. however!! addSlots does work for adding operator overloading to classes. so this does exactly what you'd expect:

Class::Prototyped->newPackage('MyClass');
MyClass->reflect->addSlots(
    '""' => sub { my $self = shift; $self->value( $self->value + 1 ) },
);

$foo = MyClass->new( value => 2 );
print $foo, "\n";

🌈 Object Class 🌈

The special parent slot class* is how we indicate an object's class. when you make Class::Prototyped objects by calling Class::Prototyped->new(), the class* slot is not set. but if you create objects by calling new on a class or object that inherits from Class::Prototyped, then class* points to the package name when new was called on a named class, or to the object when new was called on an object.

Getting that value back is easy peasy:

$foo->reflect->class;

⭐ Calling Inherited Methods ⭐

okay so this one's a whole thing. methods (and fields) inherited from prototypes or classes are not generally available through the usual Perl $self->SUPER::something() mechanism. rude, I know. 😭

Here's why: SUPER::something is hardcoded to the package where the subroutine (anonymous or otherwise) was defined. and for the vast majority of programs that's gonna be main::, so SUPER:: goes looking in @main::ISA β€” which is, respectfully, not a very useful place to look. πŸ’€

The workaround is genuinely clever: a wrapper can be automatically placed around your subroutine that stashes away the package the subroutine is attached to. then from inside the subroutine you use the super mirror method to make an inherited call. slay. BUT β€” because we'd rather not write code that guesses whether or not your subroutine uses the super construct, you have to tell addSlots that this subroutine needs the wrapper. how? use the extended addSlots syntax (check the method description for the full tea) and pass in the slot attribute 'superable'. the examples below use the minimalist form of the extended syntax.

so this code works:

use Class::Prototyped;

my $p1 = Class::Prototyped->new(
    method => sub { print "this is method in p1\n" },
);

my $p2 = Class::Prototyped->new(
    '*'                     => $p1,
    [qw(method superable)]' => sub {
        print "this is method in p2 calling method in p1: ";
        $_[0]->reflect->super('method');
    },
);

And to make life easier: if you specify :EZACCESS during the import, super can be called directly on an object instead of through its mirror. πŸŽ€

The other thing you gotta be aware of is copying methods from one object to another. the proper way:

$foo->reflect->addSlot($bar->reflect->getSlot('method'));

When getSlot is called in an array context, it returns both the complete format for the slot identifier and the slot. this makes sure slot attributes get passed along, including that superable attribute. no detail left behind. πŸ’…

And finally, to help protect your code, the super method is smart enough to figure out whether it was called inside a wrapped subroutine. if it wasn't, it croaks, telling you the method should've had the superable attribute set when it was added. want to disable that checking? there's an import option :SUPER_FAST β€” it'll improve the performance of your code, of course, but it could result in very hard to trace bugs if you haven't been careful. so like… be careful. πŸ™


πŸ”₯ PERFORMANCE NOTES πŸ”₯

Okay bestie, real talk time πŸ’…. You gotta know where the edges of prototyped-based programming actually are, especially in a language like Perl that was not built for it. Lowkey crucial.

Like, imagine this: it might make sense to make every single field in a database its own object. And then those field objects get attached to a record class. And then all of that gets implemented with Class::Prototyped. Cute in theory, right? 🌸 Except it would be very inefficient if every record that got read from the database was stored in a Class::Prototyped based object β€” unless, of course, you're storing code in the database, which, okay, different vibe entirely πŸ™ˆ.

So in that situation? Generally good to choke off the prototype-based behavior for the individual record objects. For best performance, confine Class::Prototyped to those parts of the code where behaviour is mutable from outside the module. ✨ The new method docs for Class::Prototyped spill the tea on choking off Class::Prototyped behavior.

And ngl, there are a number of performance hits when you use Class::Prototyped, compared to more traditional OO code. But here's the thing to note, bestie: these generally lie in the instantiation and creation of classes and objects, not in the actual use of them. So the hot part is the setup, not the living. πŸŽ€ The scripts in the perf directory were literally designed for benchmarking some of this material.


🧚 Class Instantiation

The normal way of creating a class is like this:

package Pack_123;
sub a {"hi";}
sub b {"hi";}
sub c {"hi";}
sub d {"hi";}
sub e {"hi";}

And the most efficient way of doing that using "proper" Class::Prototyped methodology looks like this:

Class::Prototyped->newPackage("Pack_123");
push(@P_123::slots, a => sub {"hi";});
push(@P_123::slots, b => sub {"hi";});
push(@P_123::slots, c => sub {"hi";});
push(@P_123::slots, d => sub {"hi";});
push(@P_123::slots, e => sub {"hi";});
Pack_123->reflect->addSlots(@P_123::slots);

This approach ensures that the new package gets the proper default attributes, and that the slots are created through addSlots, thus ensuring that default attributes are properly implemented. It avoids multiple calls to ->reflect->addSlot, though, which improves performance. πŸ‘‘

The idea behind pushing the slots onto an array? It enables one to intersperse code with POD, since POD is not permitted inside of a single Perl statement. Iconic workaround fr fr.

Now for the numbers, and bestie these numbers are giving pain πŸ’€: on a Pent 4 1.8GHz machine, the normal code runs in 120 usec, whereas the Class::Prototyped code runs in around 640 usec, or over 5 times slower. A straight call to addSlots with all five methods runs in around 510 usec. Code that creates the package and the mirror without adding slots runs in around 135 usec, so we're looking at an overhead of less than 100 usec per slot.

In a situation where the "compile" time dominates the "execution" time (and I'm using those terms loosely, since much of what happens in Class::Prototyped is technically execution time, but it is activity that traditionally would happen at compile time), Class::Prototyped might prove to be too much overhead. On the otherhand 🌈, you may find that demand loading can cut much of that overhead and can be implemented less painfully than might otherwise be thought.

πŸ¦‹ Object Instantiation

There is no need to even compare here. Like, at all. Blessing a hash into a class takes less than 2 usec. Creating a new Class::Prototyped object takes at least 60 or 70 times longer. 😭

The trick is to avoid creating unnecessary Class::Prototyped objects. If you know that all 10,000 database records are going to inherit all of their behavior from the parent class, there is no point in creating 10,000 packages and all the attendant overhead. The new method for Class::Prototyped demonstrates how to ensure that those state objects are created as normal Perl objects. ✨

🫢 Method Calls

Okay here's the good news and I'm obsessed: method calls are just as fast as normal Perl method calls, inherited or not. πŸ’–

Why? Because the existing Perl OO machinery has been hijacked in Class::Prototyped. (Rude. Effective. Slay.) πŸ‘‘

The exception to this is if filter slot attributes have been used, including wantarray, superable, and profile. In that situation, the added overhead is that for a normal Perl subroutine call β€” which is faster than a method call because it is a static binding.

πŸ’œ Instance Variable Access

The hash interface is not particularly fast, and neither is it good programming practice. Periodt.

Using the method interface to access fields is just as fast, however, as using normal getter/setter methods. 🌸


✨ IMPORT OPTIONS ✨

:OVERLOAD

This configures the support in Class::Prototyped for using operator overloading.

:REFLECT

This defines UNIVERSAL::reflect to return a mirror for any class. With a mirror, you can manipulate the class, adding or deleting methods, changing its inheritance hierarchy, etc. πŸ’…

:EZACCESS

This adds the methods addSlot, addSlots, deleteSlot, deleteSlots, getSlot, getSlots, and super to Class::Prototyped.

This lets you write:

$foo->addSlot(myMethod => sub {print "Hi there\n"});

instead of having to write:

$foo->reflect->addSlot(myMethod => sub {print "Hi there\n"});

The other methods in Class::Prototyped::Mirror should be accessed through a mirror β€” otherwise you'll end up with way too much name space pollution for your objects :)

Note that it is bad form for published modules to use :EZACCESS as you are polluting everyone else's namespace as well. πŸ™ˆ If you really want :EZACCESS for code you plan to publish, contact the maintainer and we'll see what we can about creating a variant of :EZACCESS that adds the shortcut methods to a single class. Also note that using :EZACCESS to do $obj->addSlot() is actually slower than doing $obj->reflect->addSlot().

:SUPER_FAST

Switches over to the fast version of super that doesn't check to see whether slots that use inherited calls were defined as superable. πŸ”₯

:NEW_MAIN

Creates a new function in main:: that creates new Class::Prototyped objects. Thus, you can write code like:

use Class::Prototyped qw(:NEW_MAIN :EZACCESS);

my $foo = new(say_hi => sub {print "Hi!\n";});
$foo->say_hi;

:TIED_INTERFACE

This is no longer supported. 😭 Sorry for the very short notice β€” if you have a specific need, please let me know and I will discuss your needs with you and determine whether they can be addressed in a manner that doesn't require you to rewrite your code, but still allows others to make use of less global control over the tied interfaces used. See Class::Prototyped::Mirror::tiedInterfacePackage for the preferred way of doing this. πŸŽ€

πŸ’– Class::Prototyped Methods πŸ’–


🫢 new() β€” Construct a new Class::Prototyped object.

okay so bestie, we're making a whole new object ✨. If you call this on a class or an object that inherits from Class::Prototyped, and class* is not being passed as a slot in the argument list, then the slot class* gets to be the very first element in the inheritance list. main character positioning, honestly πŸ‘‘

When it's called on named classes β€” either through the package name or through the object (aka MyPackage->reflect->object()) β€” class* is set to the package name. But when it's called on an object, class* gets set to the object that new was called on. 🌸

The arguments you pass get handed straight off to addSlots.

lowkey important: new calls newCore, so if you wanna override new, but you also want to make sure your changes apply to newPackage, clone, and clonePackage… you might wanna override newCore instead. iykyk πŸ’…

So like, this next one defines a fresh Class::Prototyped object with two method slots and one field slot:

my $foo = Class::Prototyped->new(
    field1 => 123,
    sub1   => sub { print "this is sub1 in foo" },
    sub2   => sub { print "this is sub2 in foo" },
);

And this one creates a new MyClass object with one field slot, plus the parent object $bar sitting at the beginning of the inheritance hierarchy β€” literally just before class*, which points to MyClass:

my $foo = MyClass->new(
    field1  => 123,
    [qw(bar* promote)] => $bar,
);

This next one makes an object that inherits behavior from $bar, with one field slot field1, and one parent slot class* that points to $bar:

my $foo = $bar->new(
    field1  => 123,
);

Now β€” if you wanna create normal Perl objects as child objects of a Class::Prototyped class, purely to improve performance (we love a glow-up πŸ¦‹), just implement your own standard Perl new method:

Class::Prototyped->newPackage('MyClass');
MyClass->reflect->addSlot(
    new => sub {
        my $class = shift;
        my $self = {};
        bless $self, $class;
        return $self;
    }
);

And it's still totally safe to use $obj->reflect->super() in code running on an object like that 🫢. Every other bit of reflection will automatically return the same results as if you were inspecting the class the object belongs to. no cap.


πŸŽ€ newPackage() β€” Construct a new Class::Prototyped object in a specific package.

Literally just like new, except instead of giving the new object an arbitrary package name (and okay, not entirely arbitrary β€” it's generally based on the hash memory address), the first argument is used as the name of the package. Boom: you got yourself a named class πŸ’–. All the same behavioral rules for class* that we just described for new apply here too (fr fr, new even calls newPackage itself).

⚠️ If the package name is already in use, this method will croak. rude of it but okay.

✨ clone() β€” Duplicate me

Duplicates an existing object or class and lets you add or override slots. The slot definition works exactly the same as in new().

my $p2 = $p1->clone(
    sub1 => sub { print "this is sub1 in p2" },
);

It calls newCore to create the new object*, so if you've overridden new, you should think about overriding clone too β€” that way any behavioral changes you made to new that would apply to clone actually get implemented 🫑. Or honestly? Just override newCore and be done. simplest path, iconic.


πŸ¦‹ clonePackage()

Just like clone, but instead of creating the new object with an arbitrary package name (again, not entirely arbitrary β€” it's generally based on the hash memory address), the first argument is used as the name of the package. This creates a named class πŸ‘‘.

⚠️ If the package name is already in use, this method will croak.

πŸ’… newCore()

This is where the core functionality of creating a new object actually lives 🧚. The first parameter you pass will be the name of the caller β€” so one of new, newPackage, clone, or clonePackage. The second parameter is the name of the package if applicable (meaning: for newPackage and clonePackage calls), or undef if it's not applicable. Everything after that is whatever slots you want added to the newly created object/package.

If it's called with new or newPackage, the class* slot gets prepended to the slot list if applicable. If it's called with clone or clonePackage, all slots on the receiver get prepended to the slot list. 🌈

If you wanna add behavior to object instantiation that needs to be present in all four of the instantiators (like, say, instance tracking πŸ‘€), it might make sense to override newCore so you only have to write that code in one single place. efficiency queen.


πŸ”₯ reflect() β€” Return a mirror for the object or class

The structure of an object gets modified by using a mirror πŸͺž. This is the equivalent of calling:

Class::Prototyped::Mirror->new($foo);

πŸ’€ destroy() β€” The destroy method for an object

You should never need to call this method. However… you might want to override it. Here's the lore: because we had to directly specify DESTROY for every single object in order to allow safe destruction during global destruction time β€” when objects may have already destroyed packages in their @ISA 😭 β€” we had to hook DESTROY for every object. So, to let the destroy behavior be overridden, you should specify a destroy method for your objects (by adding the slot), which will automatically get called by the Class::Prototyped::DESTROY method after the @ISA has been cleaned up 🧹.

This method should be defined to allow inherited method calls (so it should use "[qw(destroy superable)]" to define the method) and it should call $self->reflect->super('destroy'); at some point in the code.

Here's a quick rundown of the default destruction behavior for objects:

  • Class::Prototyped::DESTROY is called because it's linked into the package for all objects at instantiation time
  • All no longer existent entries get stripped from @ISA
  • The inheritance hierarchy gets searched for a DESTROY method that is not Class::Prototyped::DESTROY. That DESTROY method gets stashed away for a later call πŸ“¦
  • The inheritance hierarchy gets searched for a destroy method, and it's called. Note that the Class::Prototyped::destroy method β€” which will either be called directly because it shows up in the inheritance hierarchy, or indirectly through calls to $self->reflect->super('destroy'); β€” will delete all non-parent slots from the object. It leaves parent slots alone, because the destructors for parent slots shouldn't be called until the destruction of the object in question is complete (otherwise inherited destructors might still be executing even though the object they belong to has already been destroyed 😡). This means the destructors for objects referenced in non-parent slots may get called, temporarily interrupting the execution sequence in Class::Prototyped::destroy
  • The previously stashed DESTROY method gets called
  • The parent slots for the object are finally removed, which enables the destructors for any objects referenced in those parent slots to run πŸƒβ€β™€οΈ
  • Final Class::Prototyped specific cleanup gets run ✨

🌸 Class::Prototyped::Mirror Methods 🌸

These are the methods you can call on the mirror you get back from a reflect call πŸͺž. And psst β€” if you specify :EZACCESS in the use Class::Prototyped line, then addSlot, addSlots, deleteSlot, deleteSlots, getSlot, getSlots, and super all become callable directly on Class::Prototyped objects too πŸ’…. convenience!!


πŸ’– new() β€” Creates a new Class::Prototyped::Mirror object

Normally you'd call this via the reflect method, but you can call it directly to avoid using the :REFLECT import option β€” which is handy when you're reflecting on classes that aren't Class::Prototyped based πŸ¦‹.


πŸŽ€ autoloadCall()

If you add an AUTOLOAD slot to an object, you're gonna need to get the name of the subroutine being called. autoloadCall() returns the name of the subroutine, with the package name stripped off πŸ‘‘


⭐ package() β€” Returns the name of the package for the object

⭐ object() β€” Returns the object itself

⭐ class() β€” Returns the class* slot for the underlying object

⭐ dump() β€” Returns a Data::Dumper string representing the object

⭐ addSlot() β€” An alias for addSlots

πŸ’œ addSlots() β€” Add or replace slot definitions

Lets you add or replace slot definitions in the receiver 🫢

$p->reflect->addSlots(
    fred        => 'this is fred',
    doSomething => sub { print 'doing something with ' . $_[1] },
);
$p->doSomething( $p->fred );

And on top of the simple form, there's an extended syntax for specifying the slot πŸ‘€. In place of the slotname, you pass an array reference composed like so:

addSlots( [$slotName, $slotType, %slotAttributes] => $slotValue );

$slotName is just the name of the slot, including the trailing * if it's a parent slot. $slotType should be 'FIELD', 'METHOD', or 'PARENT'. %slotAttributes should be a list of attribute/value pairs. And it's super common to use qw() to cut down on typing πŸ“:

$p->reflect->addSlot(
    [qw(bar FIELD)] => "this is a field",
);

$p->reflect->addSlot(
    [qw(bar FIELD constant 1)] => "this is a constant field",
);

$p->reflect->addSlot(
    [qw(foo METHOD)] => sub { print "normal method.\n"; },
);

$p->reflect->addSlot(
    [qw(foo METHOD superable 1)] => sub { print "superable method.\n"; },
);

$p->reflect->addSlot(
    [qw(parent* PARENT)] => $parent,
);

$p->reflect->addSlot(
    [qw(parent2* PARENT promote 1)] => $parent2,
);

But to make the extended syntax a little less of a chore, these shortcuts are allowed 🎁:

  • $slotType can be omitted. In that case, the slot's type gets determined by inspecting the slot's name (to figure out if it's a parent slot) and the slot's value (to figure out whether it's a field or a method slot). The $slotType value can, however, be used to supply a reference to a code object as the value for a field slot. Note that this means FIELD, METHOD, and PARENT are not legal attribute names (because that would make parsing difficult 😭)
  • If there's only one attribute and the value is 1, then the value can be omitted

Using both of those contractions together, these are all valid short forms for the extended syntax ✨:

$p->reflect->addSlot(
    [qw(bar constant)] => "this is a constant field",
);

$p->reflect->addSlot(
    [qw(foo superable)] => sub { print "superable method.\n"; },
);

$p->reflect->addSlot(
    [qw(parent2* promote)] => $parent2,
);

The currently defined slot attributes are as follows:

🌈 "FIELD" Slots 🌈

✨ "constant" (implementor) ✨

Set this to true and your field slot becomes constant β€” like, it's locked in, bestie πŸ’…. The whole point: you can no longer change it with the $object->field($newValue) syntax. But β€” and this is important β€” the value can still be modified using hash syntax, i.e. $object->{field} = $newValue.

Where's this actually useful? If you've got an object method call that takes parameters, but you wanna replace it on a given object with a hard-coded value by using a field. Why? Because then inspecting the value of the slot through Data::Dumper is so much easier than if you use a METHOD slot to return the constant β€” code objects are opaque, and nobody has time for opaque code objects πŸ™ˆ.

✨ "autoload" (filter, rank 50) ✨

The value you pass for the FIELD slot should be a subroutine that returns the desired value. On the first access, that subroutine gets called, the return value gets hard-coded into the object by adding the slot (including all otherwise specified attributes), and then the value is returned. Iconic lazy-loading behavior πŸ¦‹.

Super useful for implementing constant slots that are costly to initialize β€” especially the ones that return lists of Class::Prototyped objects! πŸ”₯

✨ "profile" (filter, rank 80) ✨

Stores profiling information in $Class::Prototyped::Mirror::PROFILE::counts. Deets πŸ‘‡

  • If profile is set to 1, increments $counts->{$package}->{$slotName} everytime the slot is accessed.
  • If profile is set to 2, increments $counts->{$package}->{$slotName}->{$caller} everytime the slot is accessed, where $caller is $file ($line).

✨ "wantarray" (filter, rank 90) ✨

If the field specifies a reference to an array and the call is in list context, this dereferences the array and returns a list of values. Context-aware and thriving ✨

✨ "description" (advisory) ✨

Can be used to specify a description. There's no real support for this yet beyond that! πŸ’€ It's giving "placeholder attribute" energy, ngl.


🌸 "METHOD" Slots 🌸

✨ "superable" (filter, rank 10) ✨

When true, this enables the $self->reflect->super( . . . ) calls for this method slot. Yes queen, call up to your parent 🫢

✨ "profile" (filter, rank 90) ✨

See "FIELD" slots for explanation. (Same vibe, method edition.)

✨ "overload" (advisory) ✨

Set automatically for methods that implement operator overloading. You don't have to lift a finger πŸ’…

✨ "description" (advisory) ✨

See "FIELD" slots for explanation.


πŸ¦‹ "PARENT" Slots πŸ¦‹

✨ "promote" (advisory) ✨

When true, this parent slot is promoted ahead of any other parent slots on the object β€” main character parent slot, honestly πŸ‘‘. But heads up: this attribute is ephemeral β€” it is not returned by calls to getSlot.

✨ "description" (advisory) ✨

See "FIELD" slots for explanation.


πŸ’œ deleteSlot() - An alias for deleteSlots

That's it. That's the whole thing. ✨

πŸ’œ deleteSlots() - Delete one or more of the receiver's slots by name

This will let you delete existing slots in the receiver, bestie. And if those slots were defined in the receiver's inheritance hierarchy, those inherited definitions will now become available again. The glow-up is real 🌈

my $p1 = Class::Prototyped->new(
    field1 => 123,
    sub1   => sub { print "this is sub1 in p1" },
    sub2   => sub { print "this is sub2 in p1" }
);
my $p2 = Class::Prototyped->new(
    'parent*' => $p1,
    sub1      => sub { print "this is sub1 in p2" },
);
$p2->sub1;    # calls $p2.sub1
$p2->reflect->deleteSlots('sub1');
$p2->sub1;    # calls $p1.sub1
$p2->reflect->deleteSlots('sub1');
$p2->sub1;    # still calls $p1.sub1

πŸ’œ super() - Call a method defined in a parent

The call to a method defined on a parent that is obscured by the current one looks like so:

$self->reflect->super('method_name', @params);

πŸ’œ slotNames() - Returns a list of all the slot names

This is passed an optional type parameter. If specified, it should be one of 'FIELD', 'METHOD', or 'PARENT'. For instance, the following will print out a list of all slots of an object:

print join(', ', $obj->reflect->slotNames)."\n";

The following would print out a list of all field slots:

print join(', ', $obj->reflect->slotNames('FIELD')."\n";

The parent slot names are returned in the same order for which inheritance is done. Ordering matters, iykyk πŸ’…

πŸ’œ slotType() - Given a slot name, determines the type

This returns 'FIELD', 'METHOD', or 'PARENT'. It croaks if the slot is not defined for that object 😭 Yes, it will yell at you. Fair though.

πŸ’œ parents() - Returns a list of all parents

Returns a list of all parent object (or package names) for this object.

πŸ’œ allParents() - Returns a list of all parents in the hierarchy

Returns a list of all parent objects (or package names) in the object's hierarchy. The whole family tree, not just the immediate ones 🌳

πŸ’œ withAllParents() - Same as above, but includes self in the list

Same as above, but includes self in the list. Main character inclusion ✨

πŸ’œ allSlotNames() - Returns a list of all slot names defined for the entire inheritance hierarchy

Note that this will return duplicate slot names if inherited slots are obscured. No dedupe, no cap πŸ’€

πŸ’œ getSlot() - Returns the requested slot

When called in scalar context, this returns the thing in the slot. When called in list context, it returns both the complete form of the extended syntax for specifying a slot name and the thing in the slot.

There's an optional parameter that can be used to modify the format of the return value in list context. The allowable values are:

  • 'default' - the extended slot syntax and the slot value are returned
  • 'simple' - the slot name and the slot value are returned. Note that in this mode, there is no access to any attributes the slot may have
  • 'rotated' - the slot name and the following hash are returned like so:
$slotName => {
  attribs => %slotAttribs,
  type => $slotType,
  value => $slotValue
},

The latter two options are quite useful when used in conjunction with the getSlots method. 🫢

πŸ’œ getSlots() - Returns a list of all the slots

This returns a list of extended syntax slot specifiers and their values ready for sending to addSlots.

It takes first the optional parameter passed to slotNames which specifies the type of slot ('FIELD', 'METHOD', 'PARENT', or undef) and then the optional parameter passed to getSlot, which specifies the format for the return value.

CW: if the latter is 'simple', the returned values can be passed to addSlots, but any non-default slot attributes (i.e. superable or constant) will be lost 😭. If the latter is 'rotated', the returned values are completely inappropriate for passing to addSlots. Both 'simple' and 'rotated' are appropriate for assigning the return values into a hash though ✨

For instance, to add all of the field slots in $bar to $foo:

$foo->reflect->addSlots($bar->reflect->getSlots('FIELD'));

To get a list of all of the slots in the 'simple' format:

my %barSlots = $bar->reflect->getSlots(undef, 'simple');

To get a list of all of the superable method slots in the 'rotated' format:

my %barMethods = $bar->reflect->getSlots('METHOD', 'rotated');
foreach my $slotName (%barMethods) {
  delete $barMethods{$slotName}
    unless $barMethods{$slotName}->{attribs}->{superable};
}

πŸ’œ promoteParents() - This changes the ordering of the parent slots

This expects a list of parent slot names. Rules, bestie πŸ“‹:

  • There should be no duplicates.
  • All of the parent slot names should be already existing parent slots on the object.

These parent slots will be moved forward in the hierarchy in the order that they are passed. Unspecified parent slots will retain their current positions relative to other unspecified parent slots, but as a group they will be moved to the end of the hierarchy. So it's a glow-up for the ones you name and a "y'all go to the back" for the rest πŸ’…

πŸ’œ tiedInterfacePackage() - This specifies the tied interface package

This allows you to specify the sort of tied interface you wish to offer when code accesses the object as a hash reference.

  • If no parameter is passed, this will return the current tied interface package active for the object.
  • If a parameter is passed, it should specify either the package name or an alias.

The currently known aliases are:

  • default
    • This specifies Class::Prototyped::Tied::Default as the tie class. The default behavior is to allow access to existing fields, but attempts to create fields, access methods, or delete slots will croak. This is the tie class used by Class::Prototyped (unless you do something very naughty and call Class::Prototyped->reflect->tiedInterfacePackage($not_default)), and as such is the fallback behavior for classes and objects if they don't get a different value from their inheritance.
  • autovivify
    • This specifies Class::Prototyped::Tied::AutoVivify as the tie class. The behavior of this package allows access to existing fields, will automatically create field slots if they don't exist, and will allow deletion of field slots. Attempts to access or delete method or parent slots will croak.

Calls to new and clone will use the tied interface in use on the existing object/package. When reflect is called for the first time on a class package, it will use the tied interface of its first parent class (i.e. $ISA[0]). If that package has not yet had reflect called on it, it will check its parent, and so on and so forth. If none of the packages in the primary inheritance fork have been reflected upon, the value for Class::Prototyped will be used, which should be default. 🌈

πŸ’œ defaultAttributes() - get and set default attributes

This isn't particularly pretty. The general syntax looks something like:

my $temp = MyClass->reflect->defaultAttributes;
$temp->{METHOD}->{superable} = 1;
MyClass->reflect->defaultAttributes($temp);

The return value from defaultAttributes is a hash with the keys 'FIELD', 'METHOD', and 'PARENT'. The values are either undef or hash references consisting of the attributes and their default values. Modify the data structure as desired and pass it back to defaultAttributes to change the default attributes for that object or class.

Heads up: default attributes are not inherited dynamically β€” the inheritance occurs when a new object is created, but from that point on changes to a parent object are not inherited by the child. 😭

Global changes can be effected by modifying the defaultAttributes for Class::Prototyped in a sufficiently early BEGIN block. But note β€” making global changes like this is not recommended for production modules as it may interfere with other modules that rely upon Class::Prototyped. ⚠️

πŸ’œ wrap()

πŸ’œ unwrap()

πŸ’œ delegate()

delegate name => slot name β€” can be string, regex, or array of same. slot can be slot name, or object, or 2-element array with slot name or object and method name. You can delegate to a parent. 🫢

πŸŽ€ include() β€” include a package or external file

OK so bestie, imagine you wanna require some arbitrary file but slap it into the namespace of an object or a class β€” and not add anything to the parents. That's literally the whole job of include(). πŸ’…

$foo->include( 'xx.pl' );

...will include whatever's sitting inside xx.pl. Same energy for modules:

$foo->include( 'MyModule' );

...will go hunting along your @INC path for MyModule.pm and include it. ✨

Now here's the fun part β€” you can pass a second parameter, and it's the name of a subroutine you can use in your included code to refer to the object that the code is being included into. 🫢 But two whole catches: it only works as long as you don't change packages in the included code, and the subroutine gets removed after the include β€” so do not, I repeat, do NOT call it from any subroutines defined in the included code. That's a trap, fr fr. πŸ’€

So if you've got this chilling in File.pl:

    sub b {'xxx.b'}

    sub c { return thisObject(); }    # DON'T DO THIS!

    thisObject()->reflect->addSlots(
        'parent*' => 'A',
        d         => 'added.d',
        e         => sub {'xxx.e'},
    );

...and you include it like this:

$mirror->include('File.pl', 'thisObject');

...then the addSlots will work absolutely fine 😌 but if sub c gets called, it will not find thisObject(). The vibes simply do not carry over. πŸ™ˆ


✍️ AUTHOR

Written by Ned Konz, perl@bike-nomad.com and Toby Ovod-Everett, toby@ovod-everett.org. 5.005_03 porting by chromatic. πŸ‘‘

Toby Ovod-Everett is currently maintaining the package. Giving main character maintenance energy. πŸ’–


πŸ“œ LICENSE

Copyright 2001-2004 Ned Konz and Toby Ovod-Everett. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. πŸ“„βœ¨


πŸ‘€ SEE ALSO

Go give these little icons a read too, iykyk:

  • Class::SelfMethods
  • Class::Object
  • Class::Classless

And that's a wrap on the whole page, bestie!! πŸŒˆπŸŽ€ This one came from perl v5.40.0, dated 2024-09-30, under the name Class:...totyped(3pm) (yes the name is literally abbreviated like that, we love a mysterious era πŸ’…). Thanks for reading all six parts β€” you ate that. πŸ«ΆπŸ’œ

πŸ“¦ where this page comes from