NAME
   Sub::HandlesVia - alternative handles_via implementation

SYNOPSIS
    package Kitchen {
      use Moo;
      use Sub::HandlesVia;
      use Types::Standard qw( ArrayRef Str );

      has food => (
        is          => 'ro',
        isa         => ArrayRef[Str],
        handles_via => 'Array',
        default     => sub { [] },
        handles     => {
          'add_food'    => 'push',
          'find_food'   => 'grep',
        },
      );
    }

    my $kitchen = Kitchen->new;
    $kitchen->add_food('Bacon');
    $kitchen->add_food('Eggs');
    $kitchen->add_food('Sausages');
    $kitchen->add_food('Beans');

    my @foods = $kitchen->find_food(sub { /^B/i });

DESCRIPTION
   If you've used Moose's native attribute traits, or MooX::HandlesVia
   before, you should have a fairly good idea what this does.

   Why re-invent the wheel? Well, this is an implementation that should work
   okay with Moo, Moose, Mouse, and any other OO toolkit you throw at it. One
   ring to rule them all, so to speak.

   Also, unlike MooX::HandlesVia, it honours type constraints, plus it
   doesn't have the limitation that it can't mutate non-reference values.

 Using with Moo
   You should be able to use it as a drop-in replacement for
   MooX::HandlesVia.

    package Kitchen {
      use Moo;
      use Sub::HandlesVia;
      use Types::Standard qw( ArrayRef Str );

      has food => (
        is          => 'ro',
        isa         => ArrayRef[Str],
        handles_via => 'Array',
        default     => sub { [] },
        handles     => {
          'add_food'    => 'push',
          'find_food'   => 'grep',
        },
      );
    }

 Using with Mouse
   It works the same as Moo basically.

    package Kitchen {
      use Mouse;
      use Sub::HandlesVia;
      use Types::Standard qw( ArrayRef Str );

      has food => (
        is          => 'ro',
        isa         => ArrayRef[Str],
        handles_via => 'Array',
        default     => sub { [] },
        handles     => {
          'add_food'    => 'push',
          'find_food'   => 'grep',
        },
      );
    }

   You are not forced to use Types::Standard. Mouse native types should work
   fine.

    package Kitchen {
      use Mouse;
      use Sub::HandlesVia;

      has food => (
        is          => 'ro',
        isa         => 'ArrayRef[Str]',
        handles_via => 'Array',
        default     => sub { [] },
        handles     => {
          'add_food'    => 'push',
          'find_food'   => 'grep',
        },
      );
    }

   Sub::HandlesVia will also recognize MooseX::NativeTraits-style traits. It
   will jump in and handle them before MooseX::NativeTraits notices!

    package Kitchen {
      use Mouse;
      use Sub::HandlesVia;

      has food => (
        is          => 'ro',
        isa         => 'ArrayRef[Str]',
        traits      => ['Array'],
        default     => sub { [] },
        handles     => {
          'add_food'    => 'push',
          'find_food'   => 'grep',
        },
      );
    }

   (If you have a mouse in your kitchen though, that might not be very
   hygienic.)

 Using with Moose
   It works the same as Mouse basically.

    package Kitchen {
      use Moose;
      use Sub::HandlesVia;
      use Types::Standard qw( ArrayRef Str );

      has food => (
        is          => 'ro',
        isa         => ArrayRef[Str],
        handles_via => 'Array',
        default     => sub { [] },
        handles     => {
          'add_food'    => 'push',
          'find_food'   => 'grep',
        },
      );
    }

   You are not forced to use Types::Standard. Moose native types should work
   fine.

    package Kitchen {
      use Moose;
      use Sub::HandlesVia;

      has food => (
        is          => 'ro',
        isa         => 'ArrayRef[Str]',
        handles_via => 'Array',
        default     => sub { [] },
        handles     => {
          'add_food'    => 'push',
          'find_food'   => 'grep',
        },
      );
    }

   Sub::HandlesVia will also recognize native-traits-style traits. It will
   jump in and handle them before Moose notices!

    package Kitchen {
      use Moose;
      use Sub::HandlesVia;

      has food => (
        is          => 'ro',
        isa         => 'ArrayRef[Str]',
        traits      => ['Array'],
        default     => sub { [] },
        handles     => {
          'add_food'    => 'push',
          'find_food'   => 'grep',
        },
      );
    }

   (If you have a moose in your kitchen, that might be even worse than the
   mouse.)

 Using with Anything
   For Moose and Mouse, Sub::HandlesVia can use their metaobject protocols to
   grab an attribute's definition and install the methods it needs to. For
   Moo, it can wrap `has` and do its stuff that way. For other classes, you
   need to be more explicit and tell it what methods to delegate to what
   attributes.

    package Kitchen {
      use Class::Tiny {
        food => sub { [] },
      };

      use Sub::HandlesVia qw( delegations );

      delegations(
        attribute   => 'food'
        handles_via => 'Array',
        handles     => {
          'add_food'    => 'push',
          'find_food'   => 'grep',
        },
      );
    }

   Setting `attribute` to "food" means that when Sub::HandlesVia needs to get
   the food list, it will call `$kitchen->food` and when it needs to set the
   food list, it will call `$kitchen->food($value)`. If you have separate
   getter and setter methods, just do:

        attribute   => [ 'get_food', 'set_food' ],

   Or if you don't have any accessors and want Sub::HandlesVia to directly
   access the underlying hashref:

        attribute   => '{food}',

   Or maybe you have a setter, but want to use hashref access for the getter:

        attribute   => [ '{food}', 'set_food' ],

   Or maybe you still want direct access for the getter, but your object is a
   blessed arrayref instead of a blessed hashref:

        attribute   => [ '[7]', 'set_food' ],

   Or maybe your needs are crazy unique:

        attribute   => [ \&getter, \&setter ],

   The coderefs are passed the instance as their first argument, and the
   setter is also passed a value to set.

   Really, I don't think there's any object system that this won't work for!

   If you supply an arrayref with a getter and setter, it's also possible to
   supply a third argument which is a coderef or string which will be called
   as a method if needing to "reset" the value. This can be thought of like a
   default or builder.

   (The `delegations` function can be imported into Moo/Mouse/Moose classes
   too, in which case the `attribute` needs to be the same attribute name you
   passed to `has`. You cannot use a arrayref, coderef, hash key, or array
   index.)

 What methods can be delegated to?
   The following table compares Sub::HandlesVia with Data::Perl, Moose native
   traits, and MouseX::NativeTraits.

     Array ===========================================
               accessor : SubHV  DataP  Moose  Mouse
                    all : SubHV  DataP
               all_true : SubHV
                    any : SubHV                Mouse
                  apply : SubHV                Mouse
                  clear : SubHV  DataP  Moose  Mouse
                  count : SubHV  DataP  Moose  Mouse
                 delete : SubHV  DataP  Moose  Mouse
               elements : SubHV  DataP  Moose  Mouse
                  fetch :                      Mouse  (alias: get)
                  first : SubHV  DataP  Moose  Mouse
            first_index : SubHV  DataP  Moose
                flatten : SubHV  DataP
           flatten_deep : SubHV  DataP
               for_each : SubHV                Mouse
          for_each_pair : SubHV                Mouse
                    get : SubHV  DataP  Moose  Mouse
                   grep : SubHV  DataP  Moose  Mouse
                   head : SubHV  DataP
                 insert : SubHV  DataP  Moose  Mouse
               is_empty : SubHV  DataP  Moose  Mouse
                   join : SubHV  DataP  Moose  Mouse
                    map : SubHV  DataP  Moose  Mouse
                    max : SubHV
                 maxstr : SubHV
                    min : SubHV
                 minstr : SubHV
               natatime : SubHV  DataP  Moose
           not_all_true : SubHV
              pairfirst : SubHV
               pairgrep : SubHV
               pairkeys : SubHV
                pairmap : SubHV
                  pairs : SubHV
             pairvalues : SubHV
            pick_random : SubHV
                    pop : SubHV  DataP  Moose  Mouse
                  print : SubHV  DataP
                product : SubHV
                   push : SubHV  DataP  Moose  Mouse
                 reduce : SubHV  DataP  Moose  Mouse
             reductions : SubHV
                 remove :                      Mouse  (alias: delete)
                  reset : SubHV
                reverse : SubHV  DataP
                 sample : SubHV
                    set : SubHV  DataP  Moose  Mouse
          shallow_clone : SubHV  DataP  Moose
                  shift : SubHV  DataP  Moose  Mouse
                shuffle : SubHV  DataP  Moose  Mouse
       shuffle_in_place : SubHV
                   sort : SubHV  DataP  Moose  Mouse
                sort_by :                      Mouse  (sort)
          sort_in_place : SubHV  DataP  Moose  Mouse
       sort_in_place_by :                      Mouse  (sort_in_place)
                 splice : SubHV  DataP  Moose  Mouse
                  store :                      Mouse  (alias: set)
                    sum : SubHV
                   tail : SubHV  DataP
                   uniq : SubHV  DataP  Moose  Mouse
          uniq_in_place : SubHV
                uniqnum : SubHV
       uniqnum_in_place : SubHV
                uniqstr : SubHV
       uniqstr_in_place : SubHV
                unshift : SubHV  DataP  Moose  Mouse

     Bool ============================================
                    not : SubHV  DataP  Moose  Mouse
                  reset : SubHV
                    set : SubHV  DataP  Moose  Mouse
                 toggle : SubHV  DataP  Moose  Mouse
                  unset : SubHV  DataP  Moose  Mouse

     Code ============================================
                execute : SubHV  DataP  Moose  Mouse
         execute_method : SubHV         Moose  Mouse

     Counter =========================================
                    dec : SubHV  DataP  Moose  Mouse
                    inc : SubHV  DataP  Moose  Mouse
                  reset : SubHV  DataP  Moose  Mouse
                    set : SubHV         Moose  Mouse

     Hash ============================================
               accessor : SubHV  DataP  Moose  Mouse
                    all : SubHV  DataP
                  clear : SubHV  DataP  Moose  Mouse
                  count : SubHV  DataP  Moose  Mouse
                defined : SubHV  DataP  Moose  Mouse
                 delete : SubHV  DataP  Moose  Mouse
               elements : SubHV  DataP  Moose  Mouse
                 exists : SubHV  DataP  Moose  Mouse
                  fetch :                      Mouse  (alias: get)
           for_each_key : SubHV                Mouse
          for_each_pair : SubHV                Mouse
         for_each_value : SubHV                Mouse
                    get : SubHV  DataP  Moose  Mouse
               is_empty : SubHV  DataP  Moose  Mouse
                   keys : SubHV  DataP  Moose  Mouse
                     kv : SubHV  DataP  Moose  Mouse
                  reset : SubHV
                    set : SubHV  DataP  Moose  Mouse
          shallow_clone : SubHV  DataP  Moose
            sorted_keys : SubHV                Mouse
                  store :                      Mouse  (alias: set)
                 values : SubHV  DataP  Moose  Mouse

     Number ==========================================
                    abs : SubHV  DataP  Moose  Mouse
                    add : SubHV  DataP  Moose  Mouse
                    div : SubHV  DataP  Moose  Mouse
                    get : SubHV
                    mod : SubHV  DataP  Moose  Mouse
                    mul : SubHV  DataP  Moose  Mouse
                    set : SubHV         Moose
                    sub : SubHV  DataP  Moose  Mouse

     String ==========================================
                 append : SubHV  DataP  Moose  Mouse
                  chomp : SubHV  DataP  Moose  Mouse
                   chop : SubHV  DataP  Moose  Mouse
                  clear : SubHV  DataP  Moose  Mouse
                    get : SubHV
                    inc : SubHV  DataP  Moose  Mouse
                 length : SubHV  DataP  Moose  Mouse
                  match : SubHV  DataP  Moose  Mouse
                prepend : SubHV  DataP  Moose  Mouse
                replace : SubHV  DataP  Moose  Mouse
       replace_globally : SubHV                Mouse
                  reset : SubHV
                    set : SubHV
                 substr : SubHV  DataP  Moose  Mouse

 Method Chaining
   Say you have the following

        handles_via => 'Array',
        handles     => {
          'add_food'    => 'push',
          'find_food'   => 'grep',
          'remove_food' => 'pop',
        },

   Now `$kitchen->remove_food` will remove the last food on the list and
   return it. But what if we don't care about what food was removed? We just
   want to remove the food and discard it. You can do this:

        handles_via => 'Array',
        handles     => {
          'add_food'    => 'push',
          'find_food'   => 'grep',
          'remove_food' => 'pop...',
        },

   Now the `remove_food` method will return the kitchen object instead of
   returning the food. This makes it suitable for chaining method calls:

     # remove the three most recent foods
     $kitchen->remove_food->remove_food->remove_food;

 Hand Waving
   Sub::HandlesVia tries to be strict by default, but you can tell it to be
   less rigourous checking method arguments, etc using the `~` prefix:

        handles_via => 'Array',
        handles     => {
          'find_food'   => '~grep',
        },

 CodeRefs
   You can delegate to coderefs:

        handles_via => 'Array',
        handles    => {
          'find_healthiest' => sub { my $foods = shift; ... },
        }

 Named Methods
   Let's say "FoodList" is a class where instances are blessed arrayrefs of
   strings.

        isa         => InstanceOf['FoodList'],
        handles_via => 'Array',
        handles     => {
          'find_food'             => 'grep',
          'find_healthiest_food'  => 'find_healthiest',
        },

   Now `$kitchen->find_food($coderef)` does this (which breaks encapsulation
   of course):

     my @result = grep $coderef->(), @{ $kitchen->food };

   And `$kitchen->find_healthiest_food` does this:

     $kitchen->food->find_healthiest

   Basically, because `find_healthiest` isn't one of the methods offered by
   Sub::HandlesVia::HandlerList::Array, it assumes you want to call it on the
   arrayref like a proper method.

 Currying Favour
   All this talk of food is making me hungry, but as much as I'd like to eat
   a curry right now, that's not the kind of currying we're talking about.

        handles_via => 'Array',
        handles     => {
          'get_food'   => 'get',
        },

   `$kitchen->get_food(0)` will return the first item on the list.
   `$kitchen->get_food(1)` will return the second item on the list. And so
   on.

        handles_via => 'Array',
        handles     => {
          'first_food'   => [ 'get' => 0 ],
          'second_food'  => [ 'get' => 1 ],
        },

   I think you already know what this does. Right?

   And yes, currying works with coderefs.

        handles_via => 'Array',
        handles     => {
          'blargy'       => [ sub { ... }, @curried ],
        },

 Pick and Mix
       isa         => ArrayRef|HashRef,
       handles_via => [ 'Array', 'Hash' ],
       handles     => {
         the_keys     => 'keys',
         ship_shape   => 'sort_in_place',
       }

   Here you have an attribute which might be an arrayref or a hashref. When
   it's an arrayref, `$object->ship_shape` will work nicely, but
   `$object->the_keys` will fail badly.

   Still, this sort of thing can kind of make sense if you have an object
   that overloads both `@{}` and `%{}`.

   Sometime a method will be ambiguous. For example, there's a `get` method
   for both hashes and arrays. In this case, the array one will win because
   you listed it first in `handles_via`.

   But you can be specific:

       isa         => ArrayRef|HashRef,
       handles_via => [ 'Array', 'Hash' ],
       handles     => {
         get_foo => 'Array->get',
         get_bar => 'Hash->get',
       }

BUGS
   Please report any bugs to
   <http://rt.cpan.org/Dist/Display.html?Queue=Sub-HandlesVia>.

   (There are known bugs for Moose native types that do coercion.)

SEE ALSO
   Moose, MouseX::NativeTraits, Data::Perl, MooX::HandlesVia.

AUTHOR
   Toby Inkster <[email protected]>.

COPYRIGHT AND LICENCE
   This software is copyright (c) 2020 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.