Object...alPerl(3pm)

πŸ’… Migrating from Classical Perl to Object::Pad

okay so this is Object::Pad(3pm) from the User Contributed Perl Documentation section, and bestie, it's a whole guide, not just a manpage. buckle up πŸŽ€


🌸 INTRODUCTION 🌸

So Object::Pad gives you a genuinely convenient, modern syntax for writing class-based object oriented code. slay. but here's the thing β€” you probably already have a ton of code written in Perl's original style: manual calls to bless, stashing your instance data directly into hash keys, all that vintage stuff. this guide's whole job is to walk you through a sequence of steps to help you rewrite that kind of code into using Object::Pad instead. it's giving glow-up ✨

And like, besides being a good idea on its own, this often serves as your first step toward a further onwards migration to Feature::Compat::Class, and eventually the native class syntax that recent versions of Perl itself provide as the class feature. see also perlclass. iykyk πŸ’œ


πŸ¦‹ SCENARIO 1 - A SIMPLE CLASS πŸ¦‹

Let's say we've got a simple module that provides some sort of object class based on a blessed hash reference. before we start, the module file begins like this:

use v5.36;

package My::Example::Class v1.23;

sub new
{
   my $class = shift;
   my %params = @_;

   return bless {
      x => $params{x} // 0,
      y => $params{y} // 0,
   }, $class;
}

...

Over the following steps we're gonna look at a sequence of small changes you can make to this file to turn it into using Object::Pad in a good style. these steps are self-contained β€” each one can be made one at a time, while the code stays fully operational with its intended behaviour in between. and every single one of these changes is entirely internal within the source file that implements this class. the externally-visible API that other code sees? completely unmodified. no cap 🫢

As a result of that, you do not have to migrate everything all at once. you can start by altering just a few files, or making just the first few changes to some files, and the system or application as a whole keeps running exactly like it did before. this lets you gradually rewrite in stages, without needing one big disruptive change. lowkey the whole point πŸ’…


⭐ Step 1 - Basics ⭐

First thing to do: add the use Object::Pad line at the start. this gives us several new keywords β€” most notably the class keyword, which we'll use for declaring the class itself. don't stress about imports polluting the main:: namespace β€” the Object::Pad module only has lexical effects, so the scope of its additions is limited to this file alone. main character energy, stays in its lane πŸ‘‘

We'll give a module version number to this use statement. this is important, because we want to make sure we have a sufficiently recent version of the module for our current set of features.

So now our file can begin:

use v5.36;
use Object::Pad v0.800;

package My::Example::Class v1.23;

sub new
{
   ...
}

Next up, we need to declare our class by using the class keyword. you can use it in place of the package keyword. now, since we're in the process of migrating some existing code, we need to temporarily tell Object::Pad to do something odd with the object instances πŸ™ˆ

Normally, for a newly-written class, it picks its own internal representation type for instances, which is largely opaque to outside interaction. this internal type may be some kind of array reference on older Perl versions, but on versions of Perl new enough to support the class feature it actually uses the same opaque representation type that core Perl classes use. fancy ✨

But until we've finished our migration process for this class, we need to make sure it uses a blessed hash reference so the existing code we haven't rewritten yet keeps working correctly. a lot of that code will presume object instances are hash references and will try to use individually named keys inside those references to store their data. for that to keep working, we need to supply the :repr attribute and ask to specifically use blessed hash references:

use v5.36;
use Object::Pad v0.800;

class My::Example::Class v1.23 :repr(HASH);

sub new
{
   ...
}

πŸ”₯ Step 2 - Constructor πŸ”₯

At this point we could try running the code already, although honestly we haven't really changed anything yet. and if we did, we'd get one warning immediately:

Subroutine new redefined at ...

This warning shows up because Object::Pad has already provided a constructor method named new, so our code doesn't have to. so the next thing we need to do is get rid of our current new method and move that code elsewhere. bye bestie πŸ‘‹

Looking a bit more closely at our original code β€” the new method takes a hash of incoming parameters and extracts a couple of values to use as instance fields, defaulting values in case they're missing. and that includes the bless expression itself:

sub new
{
   my $class = shift;
   my %params = @_;

   return bless {
      x => $params{x} // 0,
      y => $params{y} // 0,
   }, $class;
}

We can replace this constructor with a BUILD phaser. this is a named block, much like BEGIN or similar in core Perl, which provides some code that runs at a particular time. the code in this block runs as part of the constructor that Object::Pad provided for us. it's giving scheduled ✨

Much like with the :repr attribute we added earlier, this block is more of a temporary tool to help the process of migrating existing code. it wouldn't be used in a newly-written native class β€” there are better things to use there. but for now it's required, because it gives us a way to handle the incoming arguments to the constructor and set up the initial values of those fields.

Within the scope of the BUILD phaser, a lexical variable called $self is implicitly visible; its value will be a reference to the object being constructed. this is a common theme later on β€” you don't need to specifically handle this as an argument, it's done automatically. when the BUILD phaser runs, it receives all the additional values that the caller passed to the constructor, as extra arguments into its @_ array. so we can inspect them there in the same way 🫢

We can now replace the entire sub new with this BUILD phaser:

BUILD
{
   my %params = @_;

   $self->{x} = $params{x} // 0;
   $self->{y} = $params{y} // 0;
}

Since we're on a version of Perl newer than v5.26, we can use subroutine signature syntax to tidy this block up even further. like subroutines (and as we'll see later on, methods), these BUILD phasers can be annotated with a signature, to automatically unpack the arguments passed in. so we can write it even shorter:

BUILD ( %params )
{
   $self->{x} = $params{x} // 0;
   $self->{y} = $params{y} // 0;
}

πŸŽ€ Step 3 - Method πŸŽ€

Now let's point our focus at the other methods in the file. most likely there were some existing methods designed as field accessors, and maybe other behaviour that actually performs some real work. to give some interesting variety of styles to migrate from, let's look at a few different ways the existing code might have been written:

sub x { return shift->{x}; }
sub set_x { $_[0]->{x} = $_[1]; }

sub y ( $self )
{
   return $self->{y};
}

sub set_y ( $self, $new_y )
{
   $self->{y} = $new_y;
}

sub reset
{
   my $self = shift;
   $self->{x} = $self->{y} = 0;
}

In every one of these cases, we can use another of Object::Pad's new keywords: method. a method declaration is similar to a sub, except it automatically handles the implicit $self argument at the beginning of the argument list. in each method body we already have such a variable in scope without needing to explicitly create it ourselves. iconic πŸ’…

method x { return $self->{x}; }
method set_x { $self->{x} = $_[0]; }

method y
{
   return $self->{y};
}

method set_y ( $new_y )
{
   $self->{y} = $new_y;
}

method reset
{
   $self->{x} = $self->{y} = 0;
}

In particular with the set_x method, note that since the implicit $self has been shifted out of the arguments array, the new value for the field now appears at $_[0], not $_[1] as it had done prior. ⚠️ watch out for that one, it'll get you

Like we did earlier with the BUILD phaser, we can additionally make consistent use of subroutine signatures while we're here, to get some neater handling of the other arguments passed in to these methods. and while we're at it, it's always good practice to mark an empty signature () on any methods we're not expecting to pass additional arguments into, so that at runtime it'll complain if someone accidentally does:

method x ()             { return $self->{x}; }
method set_x ( $new_x ) { $self->{x} = $new_x; }

method y ()             { return $self->{y}; }
method set_y ( $new_y ) { $self->{y} = $new_y; }

method reset ()
{
   $self->{x} = $self->{y} = 0;
}

πŸ’– Step 4 - Fields πŸ’–

One of the key benefits of using Object::Pad over plain classical Perl style is that all the fields that store data within each instance are accessible using syntax that makes them look like lexical variables, rather than hash-key access via $self. and honestly? all of our changes so far have been leading up to exactly this. so let's do it now. obsessed 🌈

So far in all of our code, we've only used the fields $self->{x} and $self->{y}. we can now declare those two using the field keyword, and replace all the occurrences in existing code with those names:

field $x;
field $y;

BUILD ( %params )
{
   $x = $params{x} // 0;
   $y = $params{y} // 0;
}

method x ()             { return $x; }
method set_x ( $new_x ) { $x = $new_x; }

method y ()             { return $y; }
method set_y ( $new_y ) { $y = $new_y; }

method reset ()
{
   $x = $y = 0;
}

At this point it may be that there's no longer any code left which tries to access $self as if it was a hash reference. all the basic field access in these methods has been updated to use the real field variables. so if we're sure we have no other code left in this file, and no other code elsewhere that β€” for example β€” tries to make any subclasses of this class, then it's safe to remove the :repr(HASH) attribute on the class line. glow-up complete ✨

use v5.36;
use Object::Pad v0.800;

class My::Example::Class v1.23;

If it's not, honestly? No stress πŸ’… it can just vibe there a little longer until the whole codebase gets its full migration glow-up. You can tidy those up at the end, fr fr.


🌸 Step 5 - Convenience Accessors 🌸

okay so at this point you might notice a little pattern happening with the accessor methods we wrote. We've got two reader methods that literally just hand back the current value of the fields, and two writer methods that just set a new value. That's it. That's the whole personality.

And because this pattern is so common β€” like, everyone's doing it β€” Object::Pad is out here handing you attributes you can annotate onto a field declaration and it'll just... build these methods for you. Obsessed. These attributes are called :reader and :writer.

field $x :reader :writer;
field $y :reader :writer;

The :reader attribute creates a reader method identical to the ones we hand-made in the previous example. By default each method gets named after the field it's attached to. Slay.

Likewise, :writer creates a writer method identical to the ones we built too. Its naming convention is that it prepends set_ to the name of the field β€” so once again its default behaviour already matches the names of the accessors we were hoping for. It ate, no notes. ✨

With these in place we can entirely delete our manually-written x, set_x, y and set_y methods. Gone. Bye. πŸ¦‹


πŸŽ€ Step 6 - Parameters and Field Defaults πŸŽ€

Remember earlier when we made the BUILD block as a temporary migration tool to help handle constructor parameters? Yeah. Now that we've got real field declarations, it's time to fix that up into a more appropriate way of working. Glow-up era.

Just like :reader and :writer on a field, there's another attribute called :param you can apply, which asks Object::Pad itself to do some implicit work β€” assigning the value of a field from a named parameter passed to the constructor.

field $x :reader :writer :param;
field $y :reader :writer :param;

As it stands, these create named parameters for the class constructor that act a lot like signature parameters without defaulting expressions β€” meaning they're mandatory. If a caller doesn't provide a corresponding value for one, an error gets raised at runtime. Not optional, bestie. 😭

My::Example::Class->new( x => 100 )

Required parameter 'y' is missing for My::Example::Class constructor at ...

So to match the behaviour of the original sub new β€” and later the BUILD phaser we temporarily added β€” we should give these fields a default value that gets applied when the caller didn't pass in something more specific. Since the original behaviour applied the value 0 using the // operator, we can preserve that exact same behaviour by using the //= assignment operator. It applies the default if the named parameter was absent, or if it was given as the value undef.

field $x :reader :writer :param //= 0;
field $y :reader :writer :param //= 0;

And since these defaulting operators fully provide the behaviour we wrote by hand in the BUILD phaser, we can delete that too. πŸ—‘οΈβœ¨


πŸ‘‘ The End Result πŸ‘‘

So now that we've done all these steps, we've fully converted our original class β€” the one written with classical Perl and manual bless expressions β€” into using all the conveniences and features of the object system that Object::Pad provides. So many behaviours that were explicitly provided by hand-written code are now just implied by standard features of the object system. Which makes them way easier to comprehend at a quick glance. There's no custom code you have to read and understand β€” instead it's the mere presence of the standard keywords and attributes doing the talking. Main character energy, honestly. πŸ’…

Having started with all that customly-written code at the beginning, we're now left with far fewer lines. The entire behaviour is captured by this:

use v5.36;
use Object::Pad v0.800;

class My::Example::Class v1.23;

field $x :reader :writer :param //= 0;
field $y :reader :writer :param //= 0;

method reset ()
{
   $x = $y = 0;
}

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


🧚 Addendum - Conventions on Field Names 🧚

okay so here's a thing β€” since these field variables are lexically scoped at the entire file level, in larger code examples it can sometimes be hard to remember they're even field variables at all. Your brain starts confusing them with signature parameters or lexicals inside individual methods and smaller bodies of code. With the old blessed-hash-reference style, the $self->{...} pattern gives you an obvious visual clue, but that kind of clue is just... lacking here. πŸ™ˆ

So it's a common style choice in larger classes to name field variables starting with a single underscore character, to distinguish them. This naming style makes it a lil easier to see at a glance β€” even when you're looking at code that's far away from the field declarations β€” that these variables actually are fields.

field $_x;
field $_y;

BUILD ( %params )
{
   $_x = $params{x} // 0;
   $_y = $params{y} // 0;
}

method reset ()
{
   $_x = $_y = 0;
}

...

And because Object::Pad knows about this naming convention of starting each field variable with a leading underscore, it will ignore that underscore for the purposes of things like handling constructor arguments for :param, or generating accessor methods for :reader and :writer. So the methods are still named x and y exactly like we want. 🎯


πŸ’– AUTHOR πŸ’–

Paul Evans <leonerd@leonerd.org.uk>


And that's a wrap, bestie! 🌈 This lil masterpiece is perl v5.42.3, dated 2026-08-22, and it's the Object...alPerl(3pm) page from the User Contributed Perl Documentation. Go forth and slay your objects πŸ’œβœ¨

πŸ“¦ where this page comes from