File Coverage

lib/List/Objects/WithUtils.pm
Criterion Covered Total %
statement 66 67 98.5
branch 21 24 87.5
condition 10 12 83.3
subroutine 11 11 100.0
total 108 114 94.7


line stmt bran cond sub code
1         package List::Objects::WithUtils;
2 101     101 use Carp;
  101        
  101        
3 101     101 use strictures 1;
  101        
  101        
4          
5         our %ImportMap = (
6           array => 'Array',
7           immarray => 'Array::Immutable',
8           array_of => 'Array::Typed',
9           immarray_of => 'Array::Immutable::Typed',
10           hash => 'Hash',
11           immhash => 'Hash::Immutable',
12           hash_of => 'Hash::Typed',
13           immhash_of => 'Hash::Immutable::Typed',
14         );
15          
16         our @DefaultImport = keys %ImportMap;
17          
18         sub import {
19 109     109   my ($class, @funcs) = @_;
20          
21 109         my $pkg;
22 109 100       if (ref $funcs[0]) {
23 16 100         croak 'Expected a list of imports or a HASH but got '.$funcs[0]
24               unless ref $funcs[0] eq 'HASH';
25 15           my %opts = %{ $funcs[0] };
  15        
26 15 50         @funcs = @{ $opts{import} || [ 'all' ] };
  15        
27 15   66       $pkg = $opts{to} || caller;
28           }
29          
30 108 100       @funcs = @DefaultImport unless @funcs;
31 108         my %fmap = map {;
32 177 100         lc( substr($_, 0, 1) eq ':' ? substr($_, 1) : $_ ) => 1
33           } @funcs;
34          
35 103 100 100     if (defined $fmap{all} || defined $fmap{-all}) {
    100 66    
36 8           @funcs = (
37               @DefaultImport,
38               'autobox'
39             )
40           } elsif (defined $fmap{functions} || defined $fmap{funcs}) {
41         # Legacy import tag, tested but not documented
42 8           @funcs = @DefaultImport
43           }
44          
45 101         my @mods;
46 101         for my $function (@funcs) {
47 185 100 100       if ($function eq 'autobox' || $function eq '-autobox') {
48 11             require List::Objects::WithUtils::Autobox;
49 11             List::Objects::WithUtils::Autobox::import($class);
50               next
51 11           }
52 177 100         if (my $thismod = $ImportMap{$function}) {
53 176             push @mods, 'List::Objects::WithUtils::'.$thismod;
54               next
55 176           }
56 4           croak "Unknown import parameter '$function'"
57           }
58          
59 98 100       $pkg = caller unless defined $pkg;
60 98         my @failed;
61 95         for my $mod (@mods) {
62 172           my $c = "package $pkg; use $mod;";
63 172     97     local $@; eval $c;
  172     25  
  87     14  
  85     9  
  88     7  
  16     7  
  39     5  
  15     5  
  4        
  4        
  26        
  26        
  26        
  2        
  5        
  5        
  5        
  26        
  26        
  26        
  26        
  2        
  2        
  5        
  2        
  5        
64 172 50         if ($@) { carp $@; push @failed, $mod }
  0        
  3        
65           }
66          
67 98 50       if (@failed) {
68 10           croak 'Failed to import ' . join ', ', @failed
69           }
70          
71           1
72 98       }
73          
74         print
75           qq[ * rjbs is patching PAUSE.\n<rjbs> (to reject anything from peregrin)\n]
76         unless caller;
77          
78         1;
79          
80         =pod
81        
82         =for Pod::Coverage import
83        
84         =head1 NAME
85        
86         List::Objects::WithUtils - List objects, kitchen sink included
87        
88         =head1 SYNOPSIS
89        
90         ## A small sample; consult the description, below, for links to
91         ## extended documentation
92        
93         # Import all object constructor functions:
94         # array immarray array_of immarray_of
95         # hash immhash hash_of immhash_of
96         use List::Objects::WithUtils;
97        
98         # Import all of the above plus autoboxing:
99         use List::Objects::WithUtils ':all';
100         # Same, but via the 'Lowu' shortcut:
101         use Lowu;
102        
103         # Most methods returning lists return new objects; chaining is easy:
104         array(qw/ aa Ab bb Bc bc /)
105         ->grep(sub { /^b/i })
106         ->map(sub { uc })
107         ->uniq
108         ->all; # ( 'BB', 'BC' )
109        
110         # Useful utilities from other list modules are available:
111         my $wanted = array(
112         +{ id => '400', user => 'bob' },
113         +{ id => '600', user => 'suzy' },
114         +{ id => '700', user => 'fred' },
115         )->first(sub { $_->{id} > 500 });
116        
117         my $itr = array( 1 .. 7 )->natatime(3);
118         while ( my @nextset = $itr->() ) {
119         ...
120         }
121        
122         my $meshed = array(qw/ a b c d /)
123         ->mesh( array(1 .. 4) )
124         ->all; # ( 'a', 1, 'b', 2, 'c', 3, 'd', 4 )
125        
126         my ($evens, $odds) = array( 1 .. 20 )
127         ->part(sub { $_[0] & 1 })
128         ->all;
129        
130         my $sorted = array(
131         +{ name => 'bob', acct => 1 },
132         +{ name => 'fred', acct => 2 },
133         +{ name => 'suzy', acct => 3 },
134         )->sort_by(sub { $_->{name} });
135        
136         # array() objects are mutable:
137         my $mutable = array(qw/ foo bar baz /);
138         $mutable->insert(1, 'quux');
139         $mutable->delete(2);
140        
141         # ... or use immarray() immutable arrays:
142         my $static = immarray( qw/ foo bar baz / );
143         $static->set(0, 'quux'); # dies
144         $static->[0] = 'quux'; # dies
145         push @$static, 'quux'; # dies
146        
147         # Construct a hash:
148         my $hash = hash( foo => 'bar', snacks => 'cake' );
149        
150         # You can set multiple keys in one call:
151         $hash->set( foobar => 'baz', pie => 'cherry' );
152        
153         # ... which is useful for merging in another (plain) hash:
154         my %foo = ( pie => 'pumpkin', snacks => 'cheese' );
155         $hash->set( %foo );
156        
157         # ... or another hash object:
158         my $second = hash( pie => 'key lime' );
159         $hash->set( $second->export );
160        
161         # Retrieve one value as a simple scalar:
162         my $snacks = $hash->get('snacks');
163        
164         # ... or retrieve multiple values as an array-type object:
165         my $vals = $hash->get('foo', 'foobar');
166        
167         # Take a hash slice of keys, return a new hash object
168         # consisting of the retrieved key/value pairs:
169         my $slice = $hash->sliced('foo', 'pie');
170        
171         # Hashes can be inflated to objects:
172         my $obj = $hash->inflate;
173         $snacks = $obj->snacks;
174        
175         # Chained method examples; methods that return multiple values
176         # typically return new array-type objects:
177         my @match_keys = $hash->keys->grep(sub { m/foo/ })->all;
178         my @match_vals = $hash->values->grep(sub { m/bar/ })->all;
179        
180         my @sorted_pairs = hash( foo => 2, bar => 3, baz => 1)
181         ->kv
182         ->sort_by(sub { $_->[1] })
183         ->all; # ( [ baz => 1 ], [ foo => 2 ], [ bar => 3 ] )
184        
185         # Perl6-inspired Junctions:
186         if ( $hash->keys->any_items eq 'snacks' ) {
187         ...
188         }
189         if ( $hash->values->all_items > 10 ) {
190         ...
191         }
192        
193         # Type-checking arrays via Type::Tiny:
194         use List::Objects::WithUtils 'array_of';
195         use Types::Standard -all;
196         my $int_arr = array_of Int() => 1 .. 10;
197        
198         # Type-checking hashes:
199         use List::Objects::WithUtils 'hash_of';
200         use Types::Standard -all;
201         my $int_hash = hash_of Int() => (foo => 1, bar => 2);
202        
203         # Native list types can be autoboxed:
204         use List::Objects::WithUtils 'autobox';
205         my $foo = [ qw/foo baz bar foo quux/ ]->uniq->sort;
206         my $bar = +{ a => 1, b => 2, c => 3 }->values->sort;
207        
208         # Autoboxing is lexically scoped like normal:
209         { no List::Objects::WithUtils::Autobox;
210         [ 1 .. 10 ]->shuffle; # dies
211         }
212        
213        
214         =head1 DESCRIPTION
215        
216         A set of roles and classes defining an object-oriented interface to Perl
217         hashes and arrays with useful utility methods, junctions, type-checking
218         ability, and optional autoboxing.
219        
220         Originally derived from L<Data::Perl>.
221        
222         =head2 Arrays
223        
224         B<array> (L<List::Objects::WithUtils::Array>) provides basic mutable
225         ARRAY-type objects. Behavior is defined by
226         L<List::Objects::WithUtils::Role::Array>; look there for documentation on
227         available methods.
228        
229         B<immarray> is imported from L<List::Objects::WithUtils::Array::Immutable> and
230         operates much like an B<array>, except methods that mutate the list are not
231         available; using immutable arrays promotes safer functional patterns.
232        
233         B<array_of> provides L<Type::Tiny>-compatible type-checking array objects
234         that can coerce and check their values as they are added; see
235         L<List::Objects::WithUtils::Array::Typed>.
236        
237         B<immarray_of> provides immutable type-checking arrays; see
238         L<List::Objects::WithUtils::Array::Immutable::Typed>.
239        
240         =head2 Hashes
241        
242         B<hash> is the basic mutable HASH-type object imported from
243         L<List::Objects::WithUtils::Hash>; see
244         L<List::Objects::WithUtils::Role::Hash> for documentation.
245        
246         B<immhash> provides immutable (restricted) hashes; see
247         L<List::Objects::WithUtils::Hash::Immutable>.
248        
249         B<hash_of> provides L<Type::Tiny>-compatible type-checking hash
250         objects; see L<List::Objects::WithUtils::Hash::Typed>.
251        
252         B<immhash_of> provides immutable type-checking hashes; see
253         L<List::Objects::WithUtils::Hash::Immutable::Typed>.
254        
255         =head2 Importing
256        
257         A bare import list (C<use List::Objects::WithUtils;>) will import all of the
258         object constructor functions described above; they can also be selectively
259         imported, e.g.:
260        
261         use List::Objects::WithUtils 'array_of', 'hash_of';
262        
263         Importing B<autobox> lexically enables L<List::Objects::WithUtils::Autobox>,
264         providing methods for native ARRAY and HASH types.
265        
266         Importing B<all> or B<:all> will import all of the object constructors and
267         additionally turn B<autobox> on; C<use Lowu;> is a shortcut for importing
268         B<all>.
269        
270         =head2 Subclassing
271        
272         The importer for this package is somewhat flexible; a subclass can override
273         import to pass import tags and a target package by feeding this package's
274         C<import()> a HASH:
275        
276         # Subclass and import to target packages (see Lowu.pm f.ex):
277         package My::Defaults;
278         use parent 'List::Objects::WithUtils';
279         sub import {
280         my ($class, @params) = @_;
281         $class->SUPER::import(
282         +{
283         import => [ 'autobox', 'array', 'hash' ],
284         to => scalar(caller)
285         }
286         )
287         }
288        
289         Functionality is mostly defined by Roles.
290         For example, it's easy to create your own array class with new methods:
291        
292         package My::Array::Object;
293         use Role::Tiny::With;
294         with 'List::Objects::WithUtils::Role::Array',
295         'List::Objects::WithUtils::Role::Array::WithJunctions';
296        
297         # An easy way to add your own functional interface:
298         use Exporter 'import'; our @EXPORT = 'my_array';
299         sub my_array { __PACKAGE__->new(@_) }
300        
301         # ... add/override methods ...
302        
303         ... in which case you may want to also define your own hash subclass that
304         overrides C<array_type> to produce your preferred arrays:
305        
306         package My::Hash::Object;
307         use Role::Tiny::With;
308         with 'List::Objects::WithUtils::Role::Hash';
309        
310         use Exporter 'import'; our @EXPORT = 'my_hash';
311         sub my_hash { __PACKAGE__->new(@_) }
312        
313         sub array_type { 'My::Array::Object' }
314        
315         # ...
316        
317         =head1 SEE ALSO
318        
319         L<List::Objects::WithUtils::Role::Array> for documentation on the basic set of
320         C<array()> methods.
321        
322         L<List::Objects::WithUtils::Role::Array::WithJunctions> for documentation on C<array()>
323         junction-returning methods.
324        
325         L<List::Objects::WithUtils::Role::Hash> for documentation regarding C<hash()>
326         methods.
327        
328         L<List::Objects::WithUtils::Array::Immutable> for more on C<immarray()>
329         immutable arrays.
330        
331         L<List::Objects::WithUtils::Array::Typed> for more on C<array_of()>
332         type-checking arrays.
333        
334         L<List::Objects::WithUtils::Array::Immutable::Typed> for more on
335         C<immarray_of()> immutable type-checking arrays.
336        
337         L<List::Objects::WithUtils::Hash::Typed> for more on C<hash_of()>
338         type-checking hashes.
339        
340         L<List::Objects::WithUtils::Hash::Immutable::Typed> for more on
341         C<immhash_of()> immutable type-checking hashes.
342        
343         L<List::Objects::WithUtils::Autobox> for details on autoboxing.
344        
345         The L<Lowu> module for a convenient importer shortcut.
346        
347         L<List::Objects::Types> for relevant L<Type::Tiny> types.
348        
349         L<MoopsX::ListObjects> for integration with L<Moops> class-building sugar.
350        
351         =head1 AUTHOR
352        
353         Jon Portnoy <avenj@cobaltirc.org>
354        
355         Licensed under the same terms as Perl.
356        
357         The original Array and Hash roles were derived from L<Data::Perl> by Matthew
358         Phillips (CPAN: MATTP), haarg, and others.
359        
360         Immutable array objects were inspired by L<Const::Fast> by Leon Timmermans
361         (CPAN: LEONT)
362        
363         Junctions are adapted from L<Perl6::Junction> by Carl Franks (CPAN: CFRANKS)
364        
365         Most of the type-checking code and other useful additions were contributed by
366         Toby Inkster (CPAN: TOBYINK)
367        
368         Much of this code simply wraps other widely-used modules, including:
369        
370         L<List::Util>
371        
372         L<List::MoreUtils>
373        
374         L<List::UtilsBy>
375        
376         =cut
377