=pod

=encoding utf-8

=head1 NAME

Marlin::Manual::Comparison - comparing Moo, Moose, C<class>, and Marlin

=head1 EXAMPLES

This section shows the same class hierarchy written with Moo, Moose,
the Perl C<class> keyword, and Marlin.

=head2 Moo

Here's a simple example of some classes and roles in L<Moo>:

  use v5.20.0;
  use experimental 'signatures';
  use Types::Common -lexical, -types;
  
  package Local::Example::Moo::NamedThing {
    use Moo;
    use MooX::StrictConstructor -late;
    use MooX::TypeTiny;
    
    has name => ( is => 'ro', isa => Str, required => 1 );
  }
  
  package Local::Example::Moo::DoesIntro {
    use Moo::Role;
    
    requires 'name';
    
    sub introduction ( $self ) {
      return sprintf( "Hi, my name is %s!", $self->name );
    }
  }
  
  package Local::Example::Moo::Person {
    use Moo;
    use MooX::StrictConstructor -late;
    
    extends 'Local::Example::Moo::NamedThing';
    with 'Local::Example::Moo::DoesIntro';
    
    has age => ( is => 'ro', predicate => 1 );
  }
  
  package Local::Example::Moo::Employee {
    use Moo;
    use MooX::StrictConstructor -late;
    
    extends 'Local::Example::Moo::Person';
    
    has employee_id => ( is => 'ro', required => 1 );
  }
  
  package Local::Example::Moo::Employee::Developer {
    use Moo;
    use MooX::StrictConstructor -late;
    use MooX::TypeTiny;
    use Sub::HandlesVia;
    
    extends 'Local::Example::Moo::Employee';
    
    has _languages => (
      init_arg    => undef,
      is          => 'lazy',
      reader      => 'get_languages',
      clearer     => 'clear_languages',
      isa         => ArrayRef[Str],
      default     => [],
      handles_via => 'Array',
      handles     => {
        add_language  => 'push',
        all_languages => 'elements',
      },
    );
    
    around introduction => sub ( $next, $self, @args ) {
      my $orig = $self->$next( @args );
      if ( my @lang = $self->all_languages ) {
        return sprintf( "%s I know: %s.", $orig, join q[, ], @lang );
      }
      return $orig;
    };
  }

=head2 Moose

Note that Moose has its own built-in type constraints and native traits,
so we'll use them instead of L<Type::Tiny> and L<Sub::HandlesVia>. Apart
from that, it's very similar to L<Moo>.

We need to remember to make each class immutable or the constructors will
be extremely slow.

  use v5.20.0;
  use experimental 'signatures';
  
  package Local::Example::Moose::NamedThing {
    use Moose;
    use MooseX::StrictConstructor;
    
    has name => ( is => 'ro', isa => 'Str', required => 1 );
    
    __PACKAGE__->meta->make_immutable;
  }
  
  package Local::Example::Moose::DoesIntro {
    use Moose::Role;
    
    requires 'name';
    
    sub introduction ( $self ) {
      return sprintf( "Hi, my name is %s!", $self->name );
    }
  }
  
  package Local::Example::Moose::Person {
    use Moose;
    use MooseX::StrictConstructor;
    
    extends 'Local::Example::Moose::NamedThing';
    with 'Local::Example::Moose::DoesIntro';
    
    has age => ( is => 'ro', predicate => 'has_age' );
    
    __PACKAGE__->meta->make_immutable;
  }
  
  package Local::Example::Moose::Employee {
    use Moose;
    use MooseX::StrictConstructor;
    
    extends 'Local::Example::Moose::Person';
    
    has employee_id => ( is => 'ro', required => 1 );
    
    __PACKAGE__->meta->make_immutable;
  }
  
  package Local::Example::Moose::Employee::Developer {
    use Moose;
    use MooseX::StrictConstructor;
    
    extends 'Local::Example::Moose::Employee';
    
    has _languages => (
      init_arg    => undef,
      is          => 'ro',
      lazy        => 1,
      reader      => 'get_languages',
      clearer     => 'clear_languages',
      isa         => 'ArrayRef[Str]',
      default     => sub ($self) { [] },
      traits      => [ 'Array' ],
      handles     => {
        add_language  => 'push',
        all_languages => 'elements',
      },
    );
    
    around introduction => sub ( $next, $self, @args ) {
      my $orig = $self->$next( @args );
      if ( my @lang = $self->all_languages ) {
        return sprintf( "%s I know: %s.", $orig, join q[, ], @lang );
      }
      return $orig;
    };
    
    __PACKAGE__->meta->make_immutable;
  }

=head2 The core C<class> keyword

  use v5.40.0;
  use experimental 'class';
  use Types::Common -lexical, -assert;
  
  class Local::Example::Core::NamedThing {
    field $name :reader :param = die "Name is required";
    
    ADJUST {
      assert_Str $name;
    }
  }
  
  package Local::Example::Core::DoesIntro {
    use Role::Tiny;
    
    requires 'name';
    
    sub introduction ( $self ) {
      return sprintf( "Hi, my name is %s!", $self->name );
    }
  }
  
  class Local::Example::Core::Person
      :isa(Local::Example::Core::NamedThing) {
    
    field $age :reader :param = undef;
    
    use Role::Tiny::With;
    with 'Local::Example::Core::DoesIntro';
    
    method has_age () {
      return defined $age;
    }
  }
  
  class Local::Example::Core::Employee
      :isa(Local::Example::Core::Person) {
    
    field $employee_id :reader :param = die "Employee id is required";
  }
  
  class Local::Example::Core::Employee::Developer
      :isa(Local::Example::Core::Employee) {
    
    field $languages :reader(get_languages) = [];
    
    method add_language ( @lang ) {
      push $languages->@*, map { assert_Str $_ } @lang;
    }
    
    method all_languages () {
      return $languages->@*;
    }
    
    method clear_languages () {
      $languages = [];
    }
    
    method introduction ( @args ) {
      my $orig = $self->next::method( @args );
      if ( my @lang = $self->all_languages ) {
        return sprintf( "%s I know: %s.", $orig, join q[, ], @lang );
      }
      return $orig;
    };
  }

=head2 Marlin

  use v5.20.0;
  use experimental 'signatures';
  use Marlin::Util -lexical, -all;
  use Types::Common -lexical, -types;
  
  package Local::Example::Marlin::NamedThing {
    use Marlin 'name!' => Str;
  }
  
  package Local::Example::Marlin::DoesIntro {
    use Marlin::Role -requires => [ 'name' ];
    
    sub introduction ( $self ) {
      return sprintf( "Hi, my name is %s!", $self->name );
    }
  }
  
  package Local::Example::Marlin::Person {
    use Marlin
      -extends => 'Local::Example::Marlin::NamedThing',
      -with    => 'Local::Example::Marlin::DoesIntro',
      qw( age? );
  }
  
  package Local::Example::Marlin::Employee {
    use Marlin
      -extends => 'Local::Example::Marlin::Person',
      qw( employee_id! );
  }
  
  package Local::Example::Marlin::Employee::Developer {
    use Marlin
      -extends => 'Local::Example::Marlin::Employee',
      -modifiers,
      _languages => {
        is          => lazy,
        isa         => ArrayRef[Str],
        init_arg    => undef,
        reader      => 'get_languages',
        clearer     => 'clear_languages',
        default     => [],
        handles_via => 'Array',
        handles     => {
          add_language  => 'push',
          all_languages => 'elements',
        }
      };
      
    around introduction => sub ( $next, $self, @args ) {
      my $orig = $self->$next( @args );
      if ( my @lang = $self->all_languages ) {
        return sprintf( "%s I know: %s.", $orig, join q[, ], @lang );
      }
      return $orig;
    };
  }

=head1 BENCHMARKS

I wrote three simple coderefs to test the constructor, accessors, and
delegated methods, plus one that uses all of these kinds of method in
combination.

Testing constructors:

  my $person_class = "Local::Example::Marlin::Person";
  my $dev_class    = "Local::Example::Marlin::Employee::Developer";
  sub {
    for my $n ( 1 .. 100 ) {
      my $o1 = $person_class->new( name => 'Alice', age => $n );
      my $o2 = $dev_class->new( name => 'Carol', employee_id => $n );
    }
  }

Testing accessors:

  my $dev_class  = "Local::Example::Marlin::Employee::Developer";
  my $dev_object = $dev_class->new( name => 'Bob', employee_id => 1 );
  sub {
    for my $n ( 1 .. 100 ) {
      my $name = $dev_object->name;
      my $id   = $dev_object->employee_id;
      my $lang = $dev_object->get_languages;
    }
  }

Testing delegated methods:

  my $dev_class  = "Local::Example::Marlin::Employee::Developer";
  my $dev_object = $dev_class->new( name => 'Bob', employee_id => 1 );
  sub {
    for my $n ( 1 .. 100 ) {
      $dev_object->add_language( $_ )
        for qw/ Perl C C++ Ruby Python Haskell SQL Go Rust Java /;
      my @all = $dev_object->all_languages;
      @all == 10 or die;
      $dev_object->clear_languages;
    }
  };

Testing a bit of everything:

  my $person_class = "Local::Example::Marlin::Person";
  my $dev_class    = "Local::Example::Marlin::Employee::Developer";
  sub {
    for my $n ( 1 .. 25 ) {
      my $person = $person_class->new( name => 'Alice', age => $n );
      my $dev    = $dev_class->new( name => 'Carol', employee_id => $n, age => 42 );
      for my $n ( 1 .. 4 ) {
        $dev->age == 42 or die;
        $dev->name eq 'Carol' or die;
        $dev->add_language( $_ )
          for qw/ Perl C C++ Ruby Python Haskell SQL Go Rust Java /;
        my @all = $dev->all_languages;
        @all == 10 or die;
        $dev->clear_languages;
      }
    }
  }

=head2 Results

The full benchmarking script is included in the distribution tarball,
so you can run it on your own machine. Exact speeds will depend on your
hardware and environment.

  [[ COMPLEX CONSTRUCTORS ]]
           Rate  Plain   Tiny    Moo  Moose   Core  Mouse Marlin
  Plain  1274/s     --    -5%   -49%   -56%   -73%   -78%   -81%
  Tiny   1334/s     5%     --   -47%   -54%   -72%   -77%   -80%
  Moo    2511/s    97%    88%     --   -13%   -48%   -58%   -63%
  Moose  2875/s   126%   115%    14%     --   -40%   -51%   -57%
  Core   4799/s   277%   260%    91%    67%     --   -19%   -28%
  Mouse  5910/s   364%   343%   135%   106%    23%     --   -12%
  Marlin 6699/s   426%   402%   167%   133%    40%    13%     --
  
  [[ COMPLEX ACCESSORS ]]
            Rate  Moose   Core  Plain   Tiny    Moo Marlin  Mouse
  Moose  17965/s     --    -8%    -9%   -11%   -48%   -58%   -62%
  Core   19504/s     9%     --    -1%    -3%   -44%   -54%   -59%
  Plain  19746/s    10%     1%     --    -2%   -43%   -54%   -58%
  Tiny   20197/s    12%     4%     2%     --   -42%   -53%   -58%
  Moo    34600/s    93%    77%    75%    71%     --   -19%   -27%
  Marlin 42666/s   137%   119%   116%   111%    23%     --   -10%
  Mouse  47574/s   165%   144%   141%   136%    37%    12%     --
  
  [[ COMPLEX DELEGATIONS ]]
           Rate   Tiny  Mouse  Plain    Moo Marlin   Core  Moose
  Tiny    792/s     --   -30%   -55%   -55%   -56%   -56%   -59%
  Mouse  1131/s    43%     --   -36%   -36%   -37%   -38%   -42%
  Plain  1767/s   123%    56%     --    -1%    -2%    -2%    -9%
  Moo    1777/s   124%    57%     1%     --    -1%    -2%    -9%
  Marlin 1801/s   128%    59%     2%     1%     --    -1%    -8%
  Core   1810/s   129%    60%     2%     2%     1%     --    -7%
  Moose  1950/s   146%    72%    10%    10%     8%     8%     --
  
  [[ COMPLEX COMBINED ]]
           Rate   Tiny  Mouse  Plain    Moo  Moose   Core Marlin
  Tiny    621/s     --   -42%   -49%   -57%   -58%   -59%   -61%
  Mouse  1067/s    72%     --   -13%   -26%   -28%   -30%   -33%
  Plain  1230/s    98%    15%     --   -15%   -17%   -19%   -23%
  Moo    1450/s   133%    36%    18%     --    -2%    -5%    -9%
  Moose  1474/s   137%    38%    20%     2%     --    -3%    -8%
  Core   1520/s   145%    43%    24%     5%     3%     --    -5%
  Marlin 1599/s   157%    50%    30%    10%     9%     5%     --

=head2 XSUB versus Pure Perl

The following table shows which methods were accellerated via XS.

  ========================================================================
  Method          Moo     Moose   Mouse   Tiny    Core    Plain   Marlin 
  ========================================================================
  [ NamedThing ]
  new             PP      PP      XS      pp      XS      PP      XS     
  name            XS      PP      XS      PP      PP      PP      XS     
  BUILDALL        pp      pp      xs      pp      --      --      XS     
  DEMOLISHALL     pp      pp      xs      --      --      --      --     
  DESTROY         --      PP      XS      pp      --      --      --     
  [ Person ]
  new             PP      PP      XS      pp      XS      PP      XS     
  name            xs      pp      xs      pp      pp      pp      XS     
  age             XS      PP      XS      PP      PP      PP      XS     
  has_age         XS      PP      XS      PP      PP      PP      XS     
  introduction    PP      PP      PP      PP      PP      PP      PP     
  BUILDALL        pp      pp      xs      pp      --      --      XS     
  DEMOLISHALL     pp      pp      xs      --      --      --      --     
  DESTROY         --      PP      XS      pp      --      --      --     
  [ Employee ]
  new             PP      PP      XS      pp      XS      PP      XS     
  name            xs      pp      xs      pp      pp      pp      XS     
  age             xs      pp      xs      pp      pp      pp      XS     
  has_age         xs      pp      xs      pp      pp      pp      XS     
  employee_id     XS      PP      XS      PP      PP      PP      XS     
  introduction    pp      pp      pp      pp      pp      pp      PP     
  BUILDALL        pp      pp      xs      pp      --      --      XS     
  DEMOLISHALL     pp      pp      xs      --      --      --      --     
  DESTROY         --      PP      XS      pp      --      --      --     
  [ Employee::Developer ]
  new             PP      PP      XS      pp      XS      PP      XS     
  name            xs      pp      xs      pp      pp      pp      XS     
  age             xs      pp      xs      pp      pp      pp      XS     
  has_age         xs      pp      xs      pp      pp      pp      XS     
  employee_id     xs      pp      xs      pp      pp      pp      XS     
  introduction    PP      PP      PP      PP      PP      PP      PP     
  get_languages   PP      PP      XS      PP      PP      PP      XS     
  all_languages   XS      PP      PP      PP      PP      PP      XS     
  add_language    XS      PP      PP      PP      PP      PP      XS     
  BUILDALL        pp      pp      xs      pp      --      --      XS     
  DEMOLISHALL     pp      pp      xs      --      --      --      --     
  DESTROY         --      PP      XS      pp      --      --      --     
  ========================================================================
  Key: XS = XSUB, PP = Pure Perl, lowercase = via inheritance.
  ========================================================================

=head1 SEE ALSO

L<Marlin::Manual::Principles> - general design principles of Marlin.

L<Marlin>.

=head1 AUTHOR

Toby Inkster E<lt>tobyink@cpan.orgE<gt>.

=head1 COPYRIGHT AND LICENCE

This software is copyright (c) 2025-2026 by Toby Inkster.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=head1 DISCLAIMER OF WARRANTIES

THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
