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.
Note: as Sub::HandlesVia needs to detect whether you're using Moo, Moose, or Mouse, and often needs to detect whether your package is a class or a role, it needs to be loaded after Moo/Moose/Mouse.
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 Mite
You should be able to use Sub::HandlesVia with Mite 0.001011 or above. Your project will still have a dependency on Sub::HandlesVia.
package MyApp::Kitchen {
use MyApp::Mite;
use Sub::HandlesVia;
has food => (
is => 'ro',
isa => 'ArrayRef[Str]',
handles_via => 'Array',
default => sub { [] },
handles => {
'add_food' => 'push',
'find_food' => 'grep',
},
);
}
If you have Mite 0.009000 or above, you can probably use its built-in handles_via
support, and avoid your project having a Sub::HandlesVia dependency!
package MyApp::Kitchen {
use MyApp::Mite;
has food => (
is => 'ro',
isa => 'ArrayRef[Str]',
handles_via => 'Array',
default => sub { [] },
handles => {
'add_food' => 'push',
'find_food' => 'grep',
},
);
}
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_list : SubHV
execute_scalar : SubHV
execute_void : SubHV
execute_method : SubHV Moose Mouse
execute_method_list : SubHV
execute_method_scalar : SubHV
execute_method_void : SubHV
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
delete_where : SubHV
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
cmp : SubHV
div : SubHV DataP Moose Mouse
eq : SubHV
ge : SubHV
get : SubHV
gt : SubHV
le : SubHV
lt : SubHV
mod : SubHV DataP Moose Mouse
mul : SubHV DataP Moose Mouse
ne : SubHV
set : SubHV Moose
sub : SubHV DataP Moose Mouse
Scalar =============================================
make_getter : SubHV
make_setter : SubHV
scalar_reference : SubHV
String =============================================
append : SubHV DataP Moose Mouse
chomp : SubHV DataP Moose Mouse
chop : SubHV DataP Moose Mouse
clear : SubHV DataP Moose Mouse
cmp : SubHV
cmpi : SubHV
contains : SubHV
contains_i : SubHV
ends_with : SubHV
ends_with_i : SubHV
eq : SubHV
eqi : SubHV
fc : SubHV
ge : SubHV
gei : SubHV
get : SubHV
gt : SubHV
gti : SubHV
inc : SubHV DataP Moose Mouse
lc : SubHV
le : SubHV
lei : SubHV
length : SubHV DataP Moose Mouse
lt : SubHV
lti : SubHV
match : SubHV DataP Moose Mouse
match_i : SubHV
ne : SubHV
nei : SubHV
prepend : SubHV DataP Moose Mouse
replace : SubHV DataP Moose Mouse
replace_globally : SubHV Mouse
reset : SubHV
set : SubHV
starts_with : SubHV
starts_with_i : SubHV
substr : SubHV DataP Moose Mouse
uc : SubHV
For further details see: Array, Bool, Code, Counter, Hash, Number, Scalar, and String.
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 https://github.com/tobyink/p5-sub-handlesvia/issues.
(There are known bugs for Moose native types that do coercion.)
SEE ALSO
Documentation for delegatable methods: Array, Bool, Code, Counter, Hash, Number, Scalar, and String.
Other implementations of the same concept: Moose::Meta::Attribute::Native, MouseX::NativeTraits, and MooX::HandlesVia with Data::Perl.
AUTHOR
Toby Inkster <tobyink@cpan.org>.
COPYRIGHT AND LICENCE
This software is copyright (c) 2020, 2022 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.