MooseX:...verview(3)

πŸ“¦
version 0.35 Β· 2 versions
, in fedora 43

🌸 NAME 🌸

MooseX::Extended::Manual::Overview β€” a work-in-progress overview for MooseX::Extended, bestie. It's the perl doc page MooseX::Extended::Manual::Overview(3) and it's gonna be a whole journey, so buckle up 🫢


✨ VERSION ✨

version 0.35 β€” yes she's at 0.35, we're thriving πŸ’…


πŸ¦‹ CONVERTING FROM MOOSE πŸ¦‹

Okay so for most sane codebases, going from Moose to MooseX::Extended is literally just:

s/Moose/MooseX::Extended/

That's it, that's the vibe. We try to be backwards-compatible, but some issues are so glaring that they genuinely can't be fixed. Run your tests, folks 😭

From there, convert your various has attributes into field or param (and run your tests, folks), and you can also delete that annoying __PACKAGE__->meta->make_immutable at the end of every package (run your tests, folks). Not me telling you three times to run your tests, I am telling you three times to run your tests 🫢

From there, if you're not using signatures, convert your methods to using signatures. You'll almost definitely get test failures there because many functions handle arguments poorly. It's giving "growth opportunity" πŸ’€

For roles, just:

s/Moose::Role/MooseX::Extended::Roles/

...and repeat the process (and run your tests, folks).

Along the way, you can probably delete your references to namespace::autoclean and friends because we provide that for you. Free cleanup, no notes, we ate πŸ’…


πŸ‘‘ RATIONALE πŸ‘‘

You can skip this section if you like, no pressure, it's optional reading πŸ“–

MooseX::Extended is built on years of experience hacking on Moose and being the lead designer of the Corinna project to bring modern OO to the Perl language β€” Corinna lives at https://github.com/Ovid/Cor if you wanna peek. We love Moose, fr fr, but over the years it's become clear that there are some problematic design choices. Plus, Corinna is not yet in core as we write this (though the Perl Steering Committee has accepted it), so for now, let's see how far we can push the envelope πŸ”₯


πŸŽ€ BOILERPLATE πŸŽ€

This:

package My::Class {
    use MooseX::Extended;

    ... your code here
}

Is sort of the equivalent to:

package My::Class {
    use v5.20.0;
    use Moose;
    use MooseX::StrictConstructor;
    use feature 'signatures';
    no warnings 'experimental::signatures';
    use namespace::autoclean;
    use Carp;
    use mro 'c3';

    ... your code here

    __PACKAGE__->meta->make_immutable;
}

1;

We get tired of typing a lot of boilerplate, so MooseX::Extended does away with it. Iconic behaviour honestly, she's efficient like that ✨


🧚 CONSTRUCTOR 🧚

The constructor behavior for Moose could use some love. Like, a lot of love πŸ’–

What's allowed in the constructor?

We've regularly faced the following problem:

package Some::Class;

use Moose;

has name     => (...);
has uuid     => (...);
has id       => (...);
has backlog  => (...);
has auth     => (...);
has username => (...);
has password => (...);
has cache    => (...);
has this     => (...);
has that     => (...);

Which of those should be passed to the constructor and which should not? Just because you can pass something to the constructor doesn't mean you should. Unfortunately, Moose defaults to "opt-out" rather than "opt-in" for constructor arguments. This makes it really easy to build objects, but it means you can pass things to the constructor and it won't always work the way you want it to 😩

There's an arcane init_arg => undef pair to pass to each one to say "this cannot be set via the constructor," but many developers are either unaware of this or simply forget about it. MooseX::Extended solves this by separating has into param (allowed in the constructor, but you can also use default or builder) and field, which is forbidden in the constructor. We can rewrite the above as this:

package Some::Class;

use MooseX::Extended;

param name     => (...);
param backlog  => (...);
param auth     => (...);
param username => (...);
param password => (...);

field cache    => (...);
field this     => (...);
field that     => (...);
field uuid     => (...);
field id       => (...);

And now you can instantly see what is and is not intended to be allowed in the constructor. Chef's kiss, clarity, we love to see it πŸ‘‘

Note that in our experience, field attributes often depend on param attributes, so they're lazy by default (a nice performance win if you don't call them), but you can still pass lazy => 0 to override this.

Unknown arguments to the constructor

Here's another fun bug:

my $object = Some::Class->new(
    name   => $name,
    seriel => $serial,
);

# later in your code

if ( $object->serial ) {
    # unreachable code
}

This is because Moose, by default, ignores any unknown arguments to the constructor β€” and in the above example, we misspelled serial as seriel. Typo bestie, we've all been there, but the silent failure is devastating πŸ’€

MooseX::Extended applies MooseX::StrictConstructor to your class so you never have to face this problem again. No more mystery unreachable code, periodt.

WARNING: Be careful when using this in existing class hierarchies. While MooseX::Extended is compatible with Moose (Moose classes can always use MooseX::Extended classes and roles), the reverse isn't always true. We've found, for example, that trying to use MooseX::StrictConstructor with DBIx::Class does not work. 🚨

Method Resolution Order (mro)

If you don't use multiple inheritance, you won't need to worry about this. However, by now it's generally agreed that the C3 method resolution order (breadth-first, for Perl) is superior to the depth-first default. You can read about the diamond inheritance problem at https://en.wikipedia.org/wiki/Multiple_inheritance if you'd like to learn more 🌈

Rather than remembering to include use mro 'c3' in your code, MooseX::Extended does it for you. She's got your back, always 🫢

If you have existing code that breaks under this, you should investigate carefully. You probably have a bug in your code. Not being mean, just being honest β€” the vibe check says it's your bug 😭

Making Your Class Immutable

It's recommended that you end your Moose classes with this:

__PACKAGE__->meta->make_immutable;

That causes a lot of things to happen under the hood. It makes your class much harder to debug because you're in a twisty maze of evaled methods, but it makes your class run much faster. The performance gain is often significant and you almost always want to use this, but it's easy to forget, so we add it for you. Main character energy: fast and forgetful, a dangerous combo ✨

To do this, we use the after_runtime function from B::Hooks::AtRuntime. However, that doesn't work under the debugger. So if you're running under the debugger, we disable this. Please keep that in mind. As a convenience, when running under the debugger, we issue a series of warnings for every class that is impacted. For a large codebase, that could be a considerable number of classes if they use MooseX::Extended:

We are running under the debugger. My::Name is not immutable
We are running under the debugger. My::Product is not immutable
We are running under the debugger. My::Order is not immutable

Like, imagine that wall of warnings scrolling by. It's giving spam, but it's informative spam πŸ’œ


🧊 IMMUTABLE OBJECTS 🧊

The subject of immutable objects has been done to death. If We set the value of an attribute but another section of the code has already fetched that value, you might have two sections of the code operating under completely different assumptions of what they're allowed to do. So by default, all attributes are read-only:

param name  => ( isa => NonEmptyStr );
field cache => ( isa => HashRef );

You can change this if you need to:

param name  => ( is => 'rw', isa => NonEmptyStr );
field cache => ( is => 'rw', isa => HashRef );

However, what's going on with that cache attribute? It returns a reference. If your code mutates that reference, every bit of code holding a reference to that object silently has its state changed. Silent state changes?? Not on our watch bestie, so we fixed that too:

field cache => ( isa => HashRef, clone => 1 );

Now, every time you get or set that data, it's cloned, ensuring that you can do this:

# assumes that the original ->cache has a SeKreT key.
my $hash1 = $object->cache;
delete $hash1->{SeKreT};
my $hash2 = $object->cache;
my $SeKreT = $hash2->{SeKreT}; # you get the original value

Internally, we use Storable's dclone method for this. Be aware that many things cannot be safely cloned (e.g., database handles). Cloning a database handle is giving chaos, don't do it πŸ™ˆ

The clone => 1 feature is safest when you don't have objects that you're trying to clone. As a result, this feature is EXPERIMENTAL. Read that again: EXPERIMENTAL 🚧

πŸ’– ATTRIBUTES πŸ’–

Okay bestie, by now you already know the vibe: is => 'ro' is the default for every single param and field attribute. Yes, all of them. You can still use has if you want, but then you gotta spell out is => ... yourself, no shortcuts for you:

has name => ( is => 'ro, ... );

But here's the thing β€” I hate typing out stuff like predicate => 'is_assigned'. Or wait, should it be predicate => 'has_assigned'?? πŸ™ˆ The indecision alone is a crime. So for a whole bunch of attributes, we made this wayyy simpler. You're welcome.

When you're using field or param, you get these little shortcut treats ✨:

param name => (
    isa       => NonEmptyStr,
    writer    => 1,   # set_name
    reader    => 1,   # get_name
    predicate => 1,   # has_name
    clearer   => 1,   # clear_name
    builder   => 1,   # _build_name
);

sub _build_name ($self) {
    ...
}

And you can also use these when you pass an array reference to the function, like:

package Point {
    use MooseX::Extended types => 'Int';

    param [ 'x', 'y' ] => (
        isa     => Int,
        clearer => 1,     # clear_x and clear_y available
        default => 0,
    ) :;
}

Note that these are just shortcuts β€” they make attributes easier to write and more consistent, which is the whole point. But you can absolutely still use the full names whenever you want:

field authz_delegate => (
    builder => '_build_my_darned_authz_delegate',
);

✨ "writer" ✨

If an attribute has writer set to 1 (the number one, not just "truthy", okay bestie, it's literally the number), a method named set_$attribute_name gets created for you.

So this:

param title => (
    isa       => Undef | NonEmptyStr,
    default   => undef,
    writer => 1,
);

Is the same as this:

has title => (
    is      => 'rw',                  # we change this from 'ro'
    isa     => Undef | NonEmptyStr,
    default => undef,
    writer  => 'set_title',
);

🌸 "reader" 🌸

By default, the reader (accessor) for the attribute is just the same as the name. Boring but reliable, you know? You can always change that though:

has payload => ( is => 'ro', reader => 'the_payload' );

However, if you want to change the reader name…

If an attribute has reader set to 1 (again, the number one), a method named get_$attribute_name is created.

So this:

param title => (
    isa       => Undef | NonEmptyStr,
    default   => undef,
    reader => 1,
);

Is the same as this:

has title => (
    is      => 'rw',                  # we change this from 'ro'
    isa     => Undef | NonEmptyStr,
    default => undef,
    reader  => 'get_title',
);

πŸ¦‹ "predicate" πŸ¦‹

If an attribute has predicate set to 1 (the number one), a method named has_$attribute_name gets conjured up.

This:

param title => (
    isa       => Undef | NonEmptyStr,
    default   => undef,
    predicate => 1,
);

Is the same as this:

has title => (
    is        => 'ro',
    isa       => Undef | NonEmptyStr,
    default   => undef,
    predicate => 'has_title',
);

πŸŽ€ "clearer" πŸŽ€

If an attribute has clearer set to 1 (the number one, one time), a method named clear_$attribute_name gets made.

This:

param title => (
    isa     => Undef | NonEmptyStr,
    default => undef,
    clearer => 1,
);

Is the same as this:

has title => (
    is      => 'ro',
    isa     => Undef | NonEmptyStr,
    default => undef,
    clearer => 'clear_title',
);

🫢 "builder" 🫢

If an attribute has builder set to 1 (the number one), you get a method named _build_$attribute_name.

This:

param title => (
    isa     =>  NonEmptyStr,
    builder => 1,
);

Is the same as this:

has title => (
    is      => 'ro',
    isa     => NonEmptyStr,
    builder => '_build_title',
);

And obviously β€” like, obviously obviously β€” a "private" attribute such as _auth_token would get a build named _build__auth_token. Note the two underscores between build and auth_token, bestie, that's not a typo, that's just how the naming math works out. πŸ’…


πŸ‘‘ AUTHOR πŸ‘‘

Curtis "Ovid" Poe <curtis.poe@gmail.com>


This software is Copyright (c) 2022 by Curtis "Ovid" Poe.

This is free software, licensed under:

The Artistic License 2.0 (GPL Compatible)

And that's a wrap on the whole page, bestie!! 🌈 This was MooseX:...verview(3) from the User Contributed Perl Documentation, generated by perl v5.42.0 and dated 2025-07-25 β€” go forth and write some iconic attributes, you absolute main character. Periodt. πŸ’–βœ¨πŸ¦‹

πŸ“¦ where this page comes from