MooseX::Extended - Extend Moose with safe defaults and useful features
version 0.03
package My::Names { use MooseX::Extended; use MooseX::Extended::Types qw(compile Num NonEmptyStr Str PositiveInt ArrayRef); use List::Util 'sum'; # the distinction between `param` and `field` makes it easier to # see which are available to `new` param _name => ( isa => NonEmptyStr, init_arg => 'name' ); param title => ( isa => Str, required => 0 ); # forbidden in the constructor field created => ( isa => PositiveInt, default => sub { time } ); sub name ($self) { my $title = $self->title; my $name = $self->_name; return $title ? "$title $name" : $name; } sub add ( $self, $args ) { state $check = compile( ArrayRef [Num, 1] ); # at least one number ($args) = $check->($args); return sum( $args->@* ); } sub warnit ($self) { carp("this is a warning"); } }
This module is ALPHA code.
This class attempts to create a safer version of Moose that defaults to read-only attributes and is easier to read and write.
It tries to bring some of the lessons learned from the Corinna project, while acknowledging that you can't always get what you want (such as true encapsulation and true methods).
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 qw( signatures postderef postderef_qq); no warnings qw( experimental::signatures experimental::postderef ); use namespace::autoclean; use Carp; use mro 'c3'; ... your code here __PACKAGE__->meta->make_immutable; } 1;
It also exports two functions which are similar to Moose has: param and field.
has
param
field
A param is a required parameter (defaults may be used). A field is not allowed to be passed to the constructor.
Note that the has function is still available, even if it's not needed.
You no longer need to end your Moose classes with:
__PACKAGE__->meta->make_immutable;
That prevents further changes to the class and provides some optimizations to make the code run much faster. However, it's somewhat annoying to type. We do this for you, via B::Hooks::AtRuntime. You no longer need to do this yourself.
B::Hooks::AtRuntime
By default, attributes defined via param and field are read-only. However, if they contain a reference, you can fetch the reference, mutate it, and now everyone with a copy of that reference has mutated state.
To handle that, we offer a new clone => $clone_type pair for attributes.
clone => $clone_type
See the MooseX::Extended::Manual::Cloning documentation.
Objection construction for MooseX::Extended is like Moose, so no changes are needed. However, in addition to has, we also provide param and field attributes, both of which are is => 'ro' by default.
is => 'ro'
The param is required, whether by passing it to the constructor, or using default or builder.
default
builder
The field is forbidden in the constructor and lazy by default.
Here's a short example:
package Silly::Name { use MooseX::Extended; use MooseX::Extended::Types qw(compile Num NonEmptyStr Str); # these default to 'ro' (but you can override that) and are required param _name => ( isa => NonEmptyStr, init_arg => 'name' ); param title => ( isa => Str, required => 0 ); # fields must never be passed to the constructor # note that ->title and ->name are guaranteed to be set before # this because fields are lazy by default field name => ( isa => NonEmptyStr, default => sub ($self) { my $title = $self->title; my $name = $self->_name; return $title ? "$title $name" : $name; }, ); }
See MooseX::Extended::Manual::Construction for a full explanation.
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) { ... }
See MooseX::Extended::Manual::Shortcuts for a full explanation.
The following Moose code will print WhoAmI. However, the second attribute name is clearly invalid.
WhoAmI
package Some::Class { use Moose; has name => ( is => 'ro' ); has '-bad' => ( is => 'ro' ); } my $object = Some::Class->new( name => 'WhoAmI' ); say $object->name;
MooseX::Extended will throw a Moose::Exception::InvalidAttributeDefinition exception if it encounters an illegal method name for an attribute.
MooseX::Extended
Moose::Exception::InvalidAttributeDefinition
This also applies to various attributes which allow method names, such as clone, builder, clearer, writer, reader, and predicate.
clone
clearer
writer
reader
predicate
When running MooseX::Extended under the debugger, there are some behavioral differences you should be aware of.
Your classes won't be immutable
Ordinarily, we call __PACKAGE__->meta->make_immutable for you. This relies on B::Hooks::AtRuntime's after_runtime function. However, that runs too late under the debugger and dies. Thus, we disable this feature under the debugger. Your classes may run a bit slower, but hey, it's the debugger!
__PACKAGE__->meta->make_immutable
after_runtime
namespace::autoclean will frustrate you
namespace::autoclean
It's very frustrating when running under the debugger and doing this:
13==> my $total = sum(3,4,5); DB<4> Undefined subroutine &main::sum called at (eval 423) ...
We had removed namespace::autoclean when running under the debugger, but backed that out: https://github.com/Ovid/moosex-extreme/issues/11.
MooseX::Extended::Manual::Overview
MooseX::Extended::Manual::Construction
MooseX::Extended::Manual::Shortcuts
MooseX::Extended::Manual::Cloning
MooseX::Extended::Types is included in the distribution.
This provides core types for you.
MooseX::Extended::Role is included in the distribution.
MooseX::Extended, but for roles.
Some of this may just be wishful thinking. Some of this would be interesting if others would like to collaborate.
Tests! Many more tests! Volunteers welcome :)
We provide MooseX::Extended::Types for convenience, along with the declare function. We should write up (and test) examples of extending it.
MooseX::Extended::Types
declare
Not everyone wants everything. In particular, using MooseX::Extended with DBIx::Class will be fatal because the latter allows unknown arguments to constructors. Or someone might want their "own" extreme Moose, requiring v5.36.0 or not using the C3 mro. What's the best way to allow this?
v5.36.0
We probably want to do with with Moose::Exporter, providing our own import method and calling the one generated by Moose::Exporter, but I need to read the docs on that more carefully (and maybe the code).
Moose::Exporter
import
BEGIN::Lift
This idea maybe belongs in MooseX::Extended::OverKill, but ...
MooseX::Extended::OverKill
Quite often you see things like this:
BEGIN { extends 'Some::Parent' }
Or this:
sub serial_number; # required by a role, must be compile-time has serial_number => ( ... );
In fact, there are a variety of Moose functions which would work better if they ran at compile-time instead of runtime, making them look a touch more like native functions. My various attempts at solving this have failed, but I confess I didn't try too hard.
There are a few things you might be interested to know about this module when evaluating it.
Most of this is written with bog-standard Moose, so there's nothing terribly weird inside, but you may wish to note that we use B::Hooks::AtRuntime and true. They seem sane, but caveat emptor.
This module was originally released on github as MooseX::Extreme, but enough people pointed out that it was not extreme at all. That's why the repository is https://github.com/Ovid/moosex-extreme/.
MooseX::Extreme
MooseX::Modern
Corinna
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)
To install MooseX::Extended, copy and paste the appropriate command in to your terminal.
cpanm
cpanm MooseX::Extended
CPAN shell
perl -MCPAN -e shell install MooseX::Extended
For more information on module installation, please visit the detailed CPAN module installation guide.