# PODNAME: MooseX::Extended::Manual::Tutorial
# ABSTRACT: Building a Better Moose

__END__

=pod

=encoding UTF-8

=head1 NAME

MooseX::Extended::Manual::Tutorial - Building a Better Moose

=head1 VERSION

version 0.23

=head1 GENESIS

L<MooseX::Extended> is built on years of experience hacking on L<Moose> and
being the lead designer of the L<Corinna|https://github.com/Ovid/Cor> 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. Further,
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. Interestingly, in some respects, L<MooseX::Extended> offers more
than the initial versions of Corinna (those this won't last).

=head1 BEST PRACTICES

L<MooseX::Extended> has the philosophy of I<providing> best practices, but not
enforcing them. We try to make many best practices the default, but you can
opt out of them. For more background, see the article, L<Common Problems in
Object-Oriented
Code|https://ovid.github.io/articles/common-problems-in-object-oriented-code.html>.
That's what lead to the creation of L<MooseX::Extended>.

In particular, it's designed to make large-scale OOP systems written in Moose
easier to maintain by removing many common failure modes, while still allowing 
you full control over what features you do and do not want.

What follows is a fairly decent overview of L<MooseX::Extended>. See the
documentation of individual modules for more information.

=head2 What's the Point.pm?

Let's take a look at a simple C<Point> class in Moose. We want it to have x/y
coordinates, and the creation time as "seconds from epoch". We'd also like to
be able to "invert" points.

    package My::Point {
        use Moose;
        has 'x'       => ( is => 'rw', isa => 'Num', writer  => 'set_x' );
        has 'y'       => ( is => 'rw', isa => 'Num', writer  => 'set_y' );
        has 'created' => ( is => 'ro', isa => 'Int', default => sub {time} );

        sub invert {
            my $self = shift;
            my ( $x, $y ) = ( $self->x, $self->y );
            $self->set_x($y);
            $self->set_y($x);
        }
    }

    1;

To the casual eye, that looks fine, but there are already many issues with the above.

=over 4

=item * The class is not immutable

You almost always want to end your Moose classes with
C<< __PACKAGE__->meta->make_immutable >>. Doing this causes Moose to close the
class definition for modifications (if that doesn't make sense, don't worry
about it), and speeds up the code considerably.

=item * Dirty namespace

Currently, C<< My::Point->can('has') >> returns true, even though C<has>
should not be a method. This, along with a bunch of other functions exported
into your class by L<Moose>, can mislead your code and confuse your method
resolution order. For this reason, it's generally recommended that you use
C<namespace::autoclean> or C<namespace::clean>. To remove those functions from
your class.

=item * Unknown constructor arguments

    my $point = My::Point->new( X => 3, y => 4 );

In the above, the first named argument should be C<x>, not C<X>. Moose simply
throws away unknown constructor arguments. One way to handle this I<might> be
to set your fields as C<required>:

    has 'x' => ( is => 'rw', isa => 'Num', writer  => 'set_x', required => 1 );
    has 'x' => ( is => 'rw', isa => 'Num', writer  => 'set_y', required => 1 );

That causes C<< My::Point->new( X => 3, y => 4 ) >> to throw an exception, but
not this: C<< My::Point->new( x => 3, y => 4, z => 5 ) >>. For this trivial
example, it's probably not a big deal, but for a large codebase, where many
Moose classes might have a huge variety of confusing arguments, it's easy to
make mistakes.

For this, we recommend L<MooseX::StrictConstructor>. Unknown arguments are
fatal.

=item * Innappropriate constructor arguments

    my $point = My::Point->new( x => 3, y => 4, created => 42 );

The above works, but the author of the class almost certainly didn't intend
for you to be passing C<created> to the constructor, but to the programmer
reading the code, that's not always clear:

    has 'created' => ( is => 'ro', isa => 'Int', default => sub {time} );

The fix for this is to add C<< init_arg => undef >> to the attribute
definition and hope the maintenance programmer notices this:

    has 'created' => ( is => 'ro', isa => 'Int', init_arg => undef, default => sub {time} );

=item * Misspelled types

What if C<created> was defined like this?

    has 'created' => ( is => 'ro', isa => 'int', default => sub {time} );

The type constraint is named C<Int>, not C<int>. You won't find out about that
little issue until runtime. There are a number of ways of dealing with this,
but we recommend the L<Type::Tiny> family of type constraints. Misspelling a
type name becomes a compile-time failure:

    use Types::Standard 'Int';
    has 'created' => ( is => 'ro', isa => Int, default => sub {time} );

=item * No signatures

Let's look at our method:

        sub invert {
            my $self = shift;
            my ( $x, $y ) = ( $self->x, $self->y );
            $self->set_x($y);
            $self->set_y($x);
        }

What if someone were to write C<< $point->invert( 4, 7 ) >>? That wouldn't
make any sense, but it also wouldn't throw an exception or even a warning,
despite it obviously not being what the programmer wanted. The simplest
solution is to just use signatures:

    use feature 'signatures';
    no warnings 'experimental::signatures'; # 5.34 and below

    sub invert ($self) { ... }

=back

=head2 Fixing our Moose class

Taking all of the above into consideration, we might rewrite our Moose class
as follows:

    package My::Point {
        use Moose;
        use MooseX::StrictConstructor;
        use Types::Standard qw(Num Int);
        use feature 'signatures';
        no warnings 'experimental::signatures';
        use namespace::autoclean;

        has 'x'       => ( is => 'rw', isa => Num, writer  => 'set_x' );
        has 'y'       => ( is => 'rw', isa => Num, writer  => 'set_y' );
        has 'created' => ( is => 'ro', isa => Int, init_arg => undef, default => sub {time} );

        sub invert ($self) {
            my ( $x, $y ) = ( $self->x, $self->y );
            $self->set_x($y);
            $self->set_y($x);
        }

        __PACKAGE__->meta->make_immutable;
    }

    1;

That's a lot of boilerplate for a simple x/y point class! Out of the box (but
almost completely customisable), L<MooseX::Extended> provides the above for
you.

    package My::Point {
        use MooseX::Extended types => [qw/Num Int/];

        has [ 'x', 'y' ] => ( is => 'rw', isa => Num, writer => 1 );

        field 'created' => ( isa => Int, lazy => 0, default => sub {time} );

        sub invert ($self) {
            my ( $x, $y ) = ( $self->x, $self->y );
            $self->set_x($y);
            $self->set_y($x);
        }
    }

No need use those various modules. No need to declare the class immutable or
end it with a true value (L<MooseX::Extended> does these for you). Instead of
remembering a bunch of boilerplate, you can focus on writing your code.

=head1 INSTANCE ATTRIBUTES

In the Moose world, we use the C<has> function to declare an "attribute" to
hold instance data for your class. This function is still available,
I<unchanged> in C<MooseX::Extended>, but two new functions are now introduced,
C<param> and C<field>, which operate similarly to C<has>. Both of these
functions default to C<< is => 'ro' >>, so that may be omitted if the
attribute is read-only.

A C<param> is a required parameter (defaults may be used). A C<field> is not
intended to be passed to the constructor (but see the extended explanation
below). This makes it I<much> easier for a developer, either writing or
reading the code, to be clear about the I<intended> class interface.

So instead of this (and having the poor maintenance programmer wondering what
is and is not allowed in the constructor):

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

You have this:

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

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

Now the interface is much clearer.

=head2 C<param>

    param name => ( isa => NonEmptyStr );

A similar function to Moose's C<has>. A C<param> is required. You may pass it
to the constructor, or use a C<default> or C<builder> to supply this value.

The above C<param> definition is equivalent to:

    has name => (
        is       => 'ro',
        isa      => NonEmptyStr,
        required => 1,
    );

If you want a parameter that has no C<default> or C<builder> and can
I<optionally> be passed to the constructor, just use C<< required => 0 >>.

    param title => ( isa => Str, required => 0 );

Note that C<param>, like C<field>, defaults to read-only, C<< is => 'ro' >>.
You can override this:

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

Otherwise, it behaves like C<has>. You can pass in any arguments that C<has>
accepts.

    # we'll make it private, but allow it to be passed to the constructor
    # as `name`
    param _name   => ( isa => NonEmptyStr, init_arg => 'name' );

=head2 C<field>

    field cache => (
        isa     => InstanceOf ['Hash::Ordered'],
        default => sub { Hash::Ordered->new },
    );

A similar function to Moose's C<has>. A C<field> is not intended to be passed
to the constructor, but you can still use C<default> or C<builder>, as normal.

The above C<field> definition is equivalent to:

    has cache => (
        is       => 'ro',
        isa      => InstanceOf['Hash::Ordered'],
        init_arg => undef,        # not allowed in the constructor
        default  => sub { Hash::Ordered->new },
        lazy     => 1,
    );

Note that C<field>, like C<param>, defaults to read-only, C<< is => 'ro' >>.
You can override this:

    field some_data => ( is => 'rw', isa => NonEmptyStr );

Otherwise, it behaves like C<has>. You can pass in any arguments that C<has>
accepts.

If you pass C<field> an C<init_arg> with a defined value, the code
will usually throw a L<Moose::Exception::InvalidAttributeDefinition>
exception. However, I<if> the I<init_arg> begins with an underscore, it's
allowed. This is designed to allow developers writing tests to supply their
own values more easily.

    field cache => (
        isa      => InstanceOf ['Hash::Ordered'],
        default  => sub { Hash::Ordered->new },
        init_arg => '_cache',
    );

With the above, you can pass C<< _cache => $my_testing_cache >> in the
constructor.

A C<field> is automatically lazy if it has a C<builder> or C<default>. This is
because there's no guarantee the code will call them, but this makes it very
easy for a C<field> to rely on a C<param> value being present. It's a common
problem in Moose that attribute initialization order is alphabetical order and
if you define an attribute whose C<default> or C<builder> relies on another
attribute, you have to remember to name them correctly or declare the field as
lazy.

Note that is does mean if you need a C<field> to be initialized at
construction time, you have to take care to declare that it's not lazy:

    field created => ( isa => PositiveInt, lazy => 0, default => sub {time} );

In our opinion, this tiny little nit is a fair trade-off for this issue:

    package Person {
        use Moose;

        has name  => ( is => 'ro', required => 1 );
        has title => ( is => 'ro', required => 0 );
        has full_name => (
            is      => 'ro',
            default => sub {
                my $self  = shift;
                my $title = $self->title;
                my $name  = $self->name;
                return defined $title ? "$title $name" : $name;
            },
        );
    }

    my $person = Person->new( title => 'Doctor', name => 'Who' );
    say $person->title;
    say $person->full_name;

The code looks fine, but it doesn't work.  In the above, C<<
$person->full_name >> is always undefined because attributes are processed in
alphabetical order, so the C<full_name> default code is run I<before> C<name>
or C<title> is set. Oops!  Adding C<< lazy => 1 >> to the C<full_name>
attribute definition is required to make it work.

Here's the same code for C<MooseX::Extended>. It works correctly:

    package Person {
        use MooseX::Extended;

        param 'name';
        param 'title' => ( required => 0 );

        field full_name => (
            default => sub {
                my $self  = shift;
                my $title = $self->title;
                my $name  = $self->name;
                return defined $title ? "$title $name" : $name;
            },
        );
    }

Note that C<param> is not lazy by default, but you can add C<< lazy => 1 >> if
you need to.

B<NOTE>: We were I<sorely> tempted to change attribute field definition order from
alphabetical to declaration order, as that would also solve the above issue
(and might allow for deterministic destruction), but we decided to play it
safe.

=head2 Attribute shortcuts

When using C<field> or C<param> (but not C<has>), 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 should be self-explanatory, but see
L<MooseX::Extended::Manual::Shortcuts> for a full explanation.

=head1 EXCLUDING FEATURES

You may find some features to be annoying, or even cause potential bugs (e.g.,
if you have a C<croak> method, our importing of C<Carp::croak> will be a
problem.

For example, if you wish to eliminate L<MooseX::StrictConstructor> and the
C<carp> and C<croak> behavior:

    use MooseX::Extended excludes => [qw/StrictConstructor carp/];

You can exclude the following:

=over 4

=item * C<StrictConstructor>

    use MooseX::Extended::Role excludes => ['StrictConstructor'];

Excluding this will no longer import C<MooseX::StrictConstructor>.

=item * C<autoclean>

    use MooseX::Extended::Role excludes => ['autoclean'];

Excluding this will no longer import C<namespace::autoclean>.

=item * C<c3>

    use MooseX::Extended::Role excludes => ['c3'];

Excluding this will no longer apply the C3 mro.

=item * C<carp>

    use MooseX::Extended::Role excludes => ['carp'];

Excluding this will no longer import C<Carp::croak> and C<Carp::carp>.

=item * C<immutable>

    use MooseX::Extended::Role excludes => ['immutable'];

Excluding this will no longer make your class immutable.

=item * C<true>

    use MooseX::Extended::Role excludes => ['true'];

Excluding this will require your module to end in a true value.

=item * C<param>

    use MooseX::Extended::Role excludes => ['param'];

Excluding this will make the C<param> function unavailable.

=item * C<field>

    use MooseX::Extended::Role excludes => ['field'];

Excluding this will make the C<field> function unavailable.

=back

=head1 TYPES

We bundle L<MooseX::Extended::Types> to make it easier to have compile-time
type checks, along with type checks in functions. Here's a silly example:

    package Not::Corinna {
        use MooseX::Extended types => [qw(compile Num NonEmptyStr ArrayRef)];
        use List::Util ();

        # these default to 'ro' (but you can override that) and are required
        param _name => ( isa => NonEmptyStr, init_arg => 'name' );
        param title => ( isa => NonEmptyStr, 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;
            },
        );

        sub add ( $self, $args ) {
            state $check = compile( ArrayRef [ Num, 1 ] );
            ($args) = $check->($args);
            return List;:Util::sum( $args->@* );
        }
    }

See L<MooseX::Extended::Types> for more information.

=head1 ASSEMBLING YOUR OWN MOOSE

After you get used to C<MooseX::Extended>, you might get tired of exchanging
the old boilerplate for new boilerplate. So don't do that. Instead, create
your own.

Define your own version of L<MooseX::Extended>:

    package My::Moose::Role {
        use MooseX::Extended::Role::Custom;

        sub import {
            my ( $class, %args ) = @_;
            MooseX::Extended::Role::Custom->create(
                excludes => [qw/ carp /],
                includes => ['multi'],
                %args    # you need this to allow customization of your customization
            );
        }
    }

    # no need for a true value

And then use it:

    package Some::Class::Role {
        use My::Moose::Role types => [qw/ArrayRef Num/];

        param numbers => ( isa => ArrayRef[Num] );

        multi sub foo ($self)       { ... }
        multi sub foo ($self, $bar) { ... }
    }

See L<MooseX::Extended::Custom> for more information.

=head1 ROLES

Of course we support roles. Here's a simple role to add a C<created> field to
your class:

    package Not::Corinna::Role::Created {
        use MooseX::Extended::Role types => ['PositiveInt'];

        # mark it as non-lazy to ensure it's run at construction time
        field created => ( isa => PositiveInt, lazy => 0, default => sub {time} );
    }

And then consume like you would any other role:

    package My::Class {
        use MooseX::Extended types => [qw(compile Num NonEmptyStr Str PositiveInt ArrayRef)];

        with qw(
        Not::Corinna::Role::Created
        );

        ...
    }

See L<MooseX::Extended::Role> for information about what features it provides
and how to adjust its behavior.

=head1 MIGRATING FROM MOOSE

For a clean Moose hierarchy, switching to L<MooseX::Extended> is often as
simple at replacing Moose with MooseX::Extended and running your tests. Then
you can start deleting various bits of boilerplate in your code (such as the
C<make_immutable> call).

Unfortunately, many Moose hierarchies are not clean. You might fail on the
C<StrictConstructor>, of find that you use multiple inheritance and rely on
dfs (depth-first search) instead of the C3 mro, or maybe (horrors!), you have
classes that aren't declared as immutable and you have code that relies on
this. A brute-force approach to handling this could be the following:

    package My::Moose {
        use MooseX::Extended::Custom;

        sub import {
            my ( $class, %args ) = @_;
            MooseX::Extended::Custom->create(
                excludes => [qw/
					StrictConstructor autoclean 
					c3                carp
					immutable         true
					field             param
                /],
                %args    # you need this to pass your own import list
            );
        }
    }
	# no need for a true value

With the above, you've excluded almost everything except signatures and
postderef features (we will work on getting around that limitation). From
there, you can replace Moose with C<My::Moose> (and do something similar with
roles) and it should just work. Then, start slowing deleting the items from
C<excludes> until your tests fail and address them one-by-one.

=head1 MOOSE INTEROPERABILITY

Moose and C<MooseX::Extended> should be 100% interoperable. L<Let us know if
it's not|https://github.com/Ovid/moosex-extreme/issues>.

=head1 VERSION COMPATIBILITY

We use L<GitHub Actions|https://github.com/features/actions> to run full
continuous integration tests on versions of Perl from v.5.20.0 and up. We do
not release any code that fails any of those tests.

=head1 AUTHOR

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

=head1 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)

=cut
