NAME
    Marlin - 🐟 pretty fast class builder with most Moo/Moose features 🐟

SYNOPSIS
      use v5.20.0;
      no warnings "experimental::signatures";
  
      package Person {
        use Types::Common -lexical, -all;
        use Marlin::Util -lexical, -all;
        use Marlin
          'name!' => Str,
          'age?'  => Int;
    
        signature_for introduction => (
          method   => true,
          named    => [ audience => Optional[InstanceOf['Person']] ],
        );
    
        sub introduction ( $self, $arg ) {
          say "Hi " . $arg->audience->name . "!" if $arg->has_audience;
          say "My name is " . $self->name . ".";
        }
      }
  
      package Employee {
        use Marlin -base => 'Person', 'employee_id!';
      }
  
      my $alice = Person->new( name => 'Alice Whotfia' );
  
      my $bob = Employee->new(
        name         => 'Bob Dobalina',
        employee_id  => '007',
      );
  
      $alice->introduction( audience => $bob );

DESCRIPTION
    Marlin is a fast class builder, inspired by Moose and Moo. It supports
    most of their features, but with a different syntax. Because it uses
    Class::XSAccessor, Class::XSConstructor, and Type::Tiny::XS, it is usually
    *slightly* faster though. Especially if you keep things simple and don't
    use features that force Marlin to fall back to using Pure Perl.

    It may not be as sleek as classes built with the Perl builtin `class`
    syntax introduced in Perl v5.38.0, but has more features, is often faster,
    and it supports Perl versions as old as v5.8.8. (Some features require
    v5.12.0+.)

    Marlin was created by the developer of Type::Tiny and Sub::HandlesVia and
    integrates with them.

  Using Marlin
    Marlin does all of its work at compile time, so doesn't export keywords
    like `has` into your namespace.

   Declaring Attributes
    Any strings found in the `use Marlin` line (except a few special ones
    beginning with a dash, used to configure Marlin) will be assumed to be
    attributes you want to declare for your class.

      package Address {
        use Marlin qw( street_address locality region country postal_code );
      }
  
      my $adr = Address->new( street_address => '123 Test Street' );
      say $adr->street_address;

    Any attributes you declare will will be accepted by the constructor that
    Marlin creates for your class, and reader/getter methods will be created
    to access their values.

    Attributes can be followed by a hashref to tailor their behaviour.

      package Address {
        use Marlin::Util qw( true false );
    
        use Marlin
          street_address  => { is => 'rw', required  => true },
          locality        => { is => 'rw' },
          region          => { is => 'rw' },
          country         => { is => 'rw', required => true },
          postal_code     => { is => 'rw', predicate => 'has_pc' },
          ;
      }
  
      my $adr = Address->new(
        street_address => '123 Test Street',
        country        => 'North Pole',
      );
  
      $adr->has_pc or die;  # will die as there is no postal_code

    Some behaviours are so commonly useful that there are shortcuts for them.

      # Shortcut for: name => { required => true }
      use Marlin 'name!';
  
      # Shortcut for: name => { predicate => true }
      use Marlin 'name?';
  
      # Shortcut for: name => { is => "rwp" }
      use Marlin 'name=';
  
      # Shortcut for: name => { is => "rw" }
      use Marlin 'name==';
  
      # Shortcut for: name => { init_arg => undef }
      use Marlin 'name.';

    Using these shortcuts, our previous Address example can be written as:

      package Address {
        use Marlin qw(
          street_address==!
          locality==
          region==
          country==!
          postal_code==?
        );
      }

    The order of these trailing modifiers doesn't matter, so 'foo=?' means the
    same as 'foo?=', though in the double-equals modifier for read-write
    attributes, the equals signs cannot have a character between them.

    There are also some useful alternatives to providing a full hashref:

      use Types::Common 'Str';
  
      # Shortcut for: name => { required => true, isa => Str }
      use Marlin 'name!' => Str;
  
      # Shortcut for: name => { lazy => true, builder => sub { ... } }
      use Marlin 'name' => sub { ... };

    If we wanted to add type checks to our previous Address example, we might
    use:

      package Address {
        use Types::Common 'Str';
        use Marlin
          'street_address==!'  => Str,
          'locality=='         => Str,
          'region=='           => Str,
          'country==!'         => Str,
          'postal_code==?'     => Str,
          ;
      }

    Marlin can *almost* be used as a drop-in replacement for Class::Tiny.
    Examples from the Class::Tiny SYNOPSIS:

      # Person.pm
      package Person;
      use Marlin qw( name );
      1;
  
      # Employee.pm
      package Employee;
      use parent 'Person';
      use Marlin qw( ssn ), timestamp => sub { time };
      1;

    The only change was to remove the hashref which wrapped `timestamp => sub
    { time }`.

   Supported Features for Attributes
    See Marlin::Manual::BetterAttributes.

   Marlin Options
    Any strings passed to Marlin that have a leading dash are taken to be
    options affecting how Marlin builds your class.

    See Marlin::Manual::ClassOptions.

   Marlin Extensions
    Strings in the `use Marlin` line which start with a colon are used to load
    Marlin extensions.

    For example:

      package Local::Foobar {
        use Marlin qw( foo bar :Clone );
      }

    Will create a class called Local::Foobar with attributes "foo" and "bar",
    but use the Marlin extension Marlin::X::Clone. (This module is bundled
    with Marlin as a demonstration of how to create extensions.)

    Extensions can be followed by a hashref of arguments for the extension:

      package Local::Foobar {
        use Marlin qw( foo bar ), ':Clone' => { try => 1 };
      }

    The `try` argument is special. By setting it to true, it tells Marlin to
    only *try* to use that extension, but carry on if the extension cannot be
    loaded.

    It is also possible to write attribute-specific extensions.

      package Local::Foobar {
        use Marlin foo => { is => 'rw', ':Frobnicate' => {...} };
      }

    This will apply a role "Marlin::XAttribute::Frobnicate" to the
    Marlin::Attribute object that is used to generate accessors, etc.

  API
    Marlin provides an API of sorts.

    `my $meta = Marlin->new( @args )`
        Creates an object representing a class, but doesn't build the class
        yet.

    `my $meta = Marlin->find_meta( $class_name )`
        Returns an object representing an existing class or role. Will
        automatically import Moose and Moo classes and roles too.

    `$meta->do_setup`
        Builds the class.

    `$meta->caller`
        Returns the name of the package which called Marlin.

    `$meta->this`
        Returns the name of the class being built.

    `$meta->parents`
        Returns an arrayref of parents. Each parent is itself an arrayref with
        the first element being the class name and the second element being a
        version number.

    `$meta->roles`
        Returns an arrayref of roles. Each role is itself an arrayref with the
        first element being the class name and the second element being a
        version number.

    `$meta->attributes`
        Returns an arrayref of attributes defined in this class. Includes
        attributes from composed roles, but not inherited attributes from
        parent classes. Each attribute is either a hashref or a
        Marlin::Attribute object.

        Calling `$meta->canonicalize_attributes` will replace any hashrefs in
        this list with Marlin::Attribute objects. That method is chainable, so
        the best way to get a list of Marlin::Attribute objects is: `@{
        $meta->canonicalize_attributes->attributes }`.

    `$meta->attributes_with_inheritance`
        Like `$meta->attributes`, but includes parent classes.

    `$meta->strict`
        Boolean indicating if the constructor will be strict.

    `$meta->constructor`
        Name of the constructor method. Usually "new".

    `$meta->modifiers`
        Boolean indicating whether Marlin should export
        Class::Method::Modifiers keywords into the package.

    `$meta->inhaled_from`
        Usually undef, but may be "Moose", "Moose::Role", "Moo", "Moo::Role",
        "Mouse", "Mouse::Role", "Class::Tiny", or "Class::XSConstructor" to
        indicate that this metadata was imported from another OO framework.

    `$meta->short_name`
        The package name without any colons. This is used in the
        stringification provided by `to_string`.

    `$meta->is_struct`
        Boolean indicating that the class was created by Marlin::Struct.

    `$meta->to_string( $object )`
        Stringifies the object to a representation useful in debugging, etc.

    `$meta->to_arrayref( $object )`
        Creates an arrayref representation of the object which closely
        resembles the string representation.

    `Marlin->can_lexical`
        Returns true if Marlin is running in an environment that supports
        lexical subs.

BUGS
    Please report any bugs to <https://github.com/tobyink/p5-marlin/issues>.

SEE ALSO
    Marlin::Role, Marlin::Struct, Marlin::Util.

    Marlin::Manual::Beginning, Marlin::Manual::BetterAttributes,
    Marlin::Manual::BetterMethods, Marlin::Manual::ClassOptions,
    Marlin::Manual::Comparison, Marlin::Manual::Principles.

    Example extensions: Marlin::X::Clone, Marlin::X::ToHash,
    Marlin::X::UndefTolerant, Marlin::XAttribute::LocalWriter,
    Marlin::XAttribute::Lvalue.

    Modules that Marlin exposes the functionality of: Class::XSAccessor,
    Class::XSConstructor, Types::Common, Type::Params, and Sub::HandlesVia.

    Inspirations: Moose and Moo.

    See also: MooseX::Marlin, MooX::Marlin.

    Even more Moose-like syntax: Marlin::Antlers.

AUTHOR
    Toby Inkster <tobyink@cpan.org>.

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.

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.

    🐟

