NAME
MooseX::Extended::Manual::Overview - Work-in-progress overview for MooseX::Extended
VERSION
version 0.10
CONVERTING FROM MOOSE
For most sane codebases, converting from Moose to MooseX::Extended is as simple as s/Moose/MooseX::Extended/
. We try to be backwards-compatible, but some issues are glaring enough that they can't be fixed. Run your tests, folks.
From there, convert your various has
attributes to field
or param
(and run your tests), and you can also delete the annoying __PACKAGE__->meta->make_immutable
at the end of every package (run your tests, folks).
From there, if you're not using signatures, convert your methods to using signatures. You'll almost definitely get test failures there as many functions handle arguments poorly.
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.
RATIONALE
You can skip this section if you like.
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. We love Moose, 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.
CONSTRUCTOR
The constructor behavior for Moose could use some love.
What's allowed in the constructor?
We've regularly face 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 means that 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 to say "this cannot be set via the constructor," but many developers are either unaware of this is simply forget about it. MooseX::Extended
solves with 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.
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".
MooseX::Extended
applies MooseX::StrictConstructor
to your class so you never have to face this problem again.
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 if you'd like to learn more.
Rather than remembering to include use mro 'c3'
in your code, MooseX::Extended does it for you.
If you have existing code that breaks under this, you should investigate carefully. You probably have a bug in your code.
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 as you're in a twisty maze of eval
ed 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.
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
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. 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).
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.
ATTRIBUTES
By now, you know that is => 'ro'
is the default for all param
and field
attributes. You can still use has
, but you will still need to use is => ...
with that:
has name => ( is => 'ro, ... );
However, I hate typing out something like predicate => 'is_assigned'
. Or should it be predicate => 'has_assigned'
? For a variety of attributes, we've made this simpler.
When using field
or param
, we have some attribute shortcuts:
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) {
...
}
These can also be used when you pass an array reference to the function:
package Point {
use MooseX::Extended types => [qw/Int/];
param [ 'x', 'y' ] => (
isa => Int,
clearer => 1, # clear_x and clear_y available
default => 0,
) :;
}
Note that these are shortcuts and they make attributes easier to write and more consistent. However, you can still use full names:
field authz_delegate => (
builder => '_build_my_darned_authz_delegate',
);
writer
If an attribute has writer
is set to 1
(the number one), a method named set_$attribute_name
is created.
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 the same as the name. You can always change this:
has payload => ( is => 'ro', reader => 'the_payload' );
However, if you want to change the reader name
If an attribute has reader
is set to 1
(the number one), a method named get_$attribute_name
is created.
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
is set to 1
(the number one), a method named has_$attribute_name
is created.
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
is set to 1
(the number one), a method named clear_$attribute_name
is created.
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
is set to 1
(the number one), 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',
);
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").
AUTHOR
Curtis "Ovid" Poe <curtis.poe@gmail.com>
COPYRIGHT AND LICENSE
This software is Copyright (c) 2022 by Curtis "Ovid" Poe.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)