File Coverage

blib/lib/Maptastic.pm
Criterion Covered Total %
statement 113 139 81.2
branch 43 56 76.7
condition 2 2 100.0
subroutine 22 32 68.7
pod 14 18 77.7
total 194 247 78.5


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -w
2              
3 2     2   50441 use strict;
  2         7  
  2         157  
4              
5             =head1 NAME
6              
7             Maptastic - all map, all the time. Maperiffic baby, yeah!
8              
9             =head1 SYNOPSIS
10              
11             use Maptastic qw(:perly);
12              
13             @a = (1, 2, 3);
14             @b = qw(Mary Jane);
15             @c = ('A' .. 'E');
16             %d = ( smokey => 1,
17             cheese => 6,
18             fire => 7,
19             plant => 3.5 );
20              
21             @spliced = map_shift { [ @_ ] } (\@a, \@b, \@c);
22              
23             @mixed = map_for { [ @_ ] } (\@a, \@b, \@c);
24              
25             %hashed = map_each { ( $_[0] > 4 ? @_ : () ) } \%d;
26              
27             =head2 Results after the above
28              
29             # map_shift / mapcaru
30             @spliced = ([1, "Mary", "A"],
31             [2, "Jane", "B"],
32             [3, undef, "C"],
33             [undef, undef, "D"],
34             [undef, undef, "E"]);
35              
36             # map_for / mapcar
37             @mixed = ([1, "Mary", "A"],
38             [2, "Jane", "B"], # some LISPs stop here
39             [3, "C"],
40             [ "D"],
41             [ "E"]);
42              
43             # map_each
44             %hashed = ( cheese => 6,
45             fire => 7 );
46              
47             =head1 DESCRIPTION
48              
49             This module defines two maptabulous new varieties of that
50             long-favourite map (see L). Two of these maps are more
51             maplicious than map itself - because unlike vanilla map, it maps more
52             than a single list! Mapendous!
53              
54             But the mappy feast does not stop there! No, to satisfy your
55             ever-growing map cravings, there's a mapdiddlyumtious version of the
56             original map that iterates over hashes! Mapnificent!
57              
58             =head2 Iterator versions
59              
60             Despite just how mapfect code looks with the flexmapible mapower of
61             map, sometimes, you don't want to process amapn entire list via map at
62             once.
63              
64             To cater for these specialist map tastes, our maxperts have come up
65             with a great new flavour for all map-like functions: iterators.
66              
67             An iterator is an object that returns the next item from its list when
68             asked. There are many ways of `asking' an iterator for it's next
69             value, as well as different semantics for `rewinding' the iterator to
70             the beginning, if possible.
71              
72             But don't worry, Maptastic is so mapscendant that it's looked at
73             all[*] of the modules on that mapreme Perl source repository, CPAN,
74             and therefore accepts the following semantics for iterators:
75              
76             =over
77              
78             =item B
79              
80             If the object to be mapped over understands the method __next__, then
81             Object::Iterate style iteration is performed.
82              
83             =item B
84              
85             If the object to be mapped is a CODE reference (even blessed), then it
86             is assumed that calling the code reference will perform the iteration.
87             With these semantics, if I is ever returned, the iterator is
88             assumed to be `spent', and is unlinked; just in case subsequent calls
89             re-start the iterator.
90              
91             =item B
92              
93             Iterator function: get_next
94              
95             =item B
96              
97             Other styles of iteration are automatically detected: is the object
98             implements a ->NEXT() or ->next() method, these are used as the
99             iterator method.
100              
101             =item B
102              
103             A filehandle is a type of iterator - so the "readline" method is
104             accepted too.
105              
106             =back
107              
108             =cut
109              
110             package Maptastic;
111             require Exporter;
112 2     2   12 use Carp;
  2         5  
  2         208  
113 2     2   11 use Scalar::Util qw(reftype blessed);
  2         7  
  2         312  
114 2     2   10 use vars qw( $VERSION @EXPORT @ISA %EXPORT_TAGS);
  2         4  
  2         387  
115              
116             BEGIN {
117 2     2   6 $VERSION= "1.01";
118 2         14 @EXPORT= qw( mapcar mapcaru map_each map_shift map_for
119             map_foreach filter
120              
121             imap iter slurp igrep
122             imapcar imapcaru imap_each imap_shift imap_for
123             imap_foreach ifilter
124             );
125 6         39 %EXPORT_TAGS = ( lisp => [ qw(mapcar mapcaru imapcar imapcaru) ],
126 2         5 (map { $_ => [ qw(map_each map_for map_foreach
127             map_shift filter) ] }
128             qw(perly perlish perl)),
129              
130             iter => [ qw(iter slurp igrep imap imap_each
131             imap_shift imap_for imap_foreach
132             ifilter) ],
133              
134             );
135 2         3460 @ISA= qw( Exporter );
136             }
137              
138             # Adapt all of the different iterator styles to the ->() style
139             sub _adapt_iter {
140 38     38   88 my $iter = shift;
141              
142 38 100       209 return unless ref $iter;
143              
144 23 100       104 if (blessed $iter) {
    50          
    50          
    0          
145              
146             # FIXME - is this a good idea? This will probably catch all
147             # sorts of objects that we don't want to.
148 15         30 for my $method (qw(__next__ get_next NEXT next readline)) {
149 15 50       70 if ($iter->can($method)) {
150 15     58   92 return sub { $iter->$method }; # see, isn't it tidy?
  58         154  
151             }
152             }
153             # no, blessed code refs must export a sensible method
154             # return $iter if reftype $iter eq "CODE";
155              
156             } elsif ( ref $iter eq "CODE" ) {
157 0         0 return $iter;
158             } elsif ( ref $iter eq "ARRAY" ) {
159 8         11 my $i = 0;
160             return sub {
161 24 100   24   124 return if ($i > $#$iter);
162 16         48 return $iter->[$i++]
163 8         45 };
164             } elsif ( ref $iter eq "GLOB" ) {
165 0     0   0 return sub { <$iter> };
  0         0  
166             }
167              
168 0         0 return undef;
169             }
170              
171             =head1 FUNCTIONS
172              
173             =head2 map and friends
174              
175             =over
176              
177             =item mapcar { code } \@list, \@list, \@list...
178              
179             =item map_for { code } \@list, \@list, \@list...
180              
181             =item map_foreach { code } \@list, \@list, \@list...
182              
183             "mapcar" originated in LISP (the LISt Processing language). So did
184             the Perl built-in function "map". "car" is an old term coming from
185             the term "Contents of the Address part of the Register", so there.
186             This function is also available as `map_for' or `map_foreach' (because
187             with for, you stop at the end of the list).
188              
189             Note that the exact behvaviour of `mapcar' apparently varied from LISP
190             to LISP, so the version given here is the one that was widely
191             publicised on PerlMonks.
192              
193             =cut
194              
195             # This function has been updated to include support for certain types
196             # of iterators
197             sub mapcar(&@)
198             {
199 2     2 1 12 my $sub= shift;
200 2 50       11 if( ! @_ ) {
201 0         0 croak( "mapcar: Nothing to map" );
202             }
203              
204 2         6 my @which;
205              
206 2         5 for my $av ( @_ ) {
207 6 100       25 if (ref $av eq "ARRAY") {
    50          
208 3         8 push @which, undef;
209             } elsif ( my $coderef = _adapt_iter ($av) ) {
210 3         11 push @which, $coderef;
211             } else {
212 0         0 push @which, undef;
213             }
214             }
215              
216 2         5 my (@ret, $x);
217 2         5 my $all_done = 0;
218              
219 2         11 for( my $i= 0; !$all_done; $i++ ) {
220 12         76 my $c = -1;
221 12         18 $all_done = 1;
222 36         47 my @next = (map {
223 12         22 $c++;
224             ( $which[$c]
225             ? ( defined($x = $which[$c]->())
226 10         15 ? do { $all_done = 0; $x }
  10         37  
227 8     5   40 : do { $which[$c] = sub{()}; () }
  5         53  
  8         33  
228             )
229             : ( $i < @$_
230 36 100       115 ? do { $all_done = 0;
  10 100       11  
    100          
231 10         30 $_->[$i] }
232             : ()
233             ) )
234             } @_);
235              
236 12 100       57 push @ret, &$sub(@next) if @next;
237             }
238 2 50       19 return wantarray ? @ret : \@ret;
239             }
240 1     1 1 1575 sub map_for (&@) { goto \&mapcar }
241 0     0 1 0 sub map_foreach (&@) { goto \&mapcar }
242              
243             =item mapcaru { code } \@list, \@list, \@list...
244              
245             =item map_shift { code } \@list, \@list, \@list...
246              
247             "mapcaru" is a version that works similarly to `mapcar', but puts
248             I (hence the u) into locations in the input array where the
249             input list has no elements. This function is also available as
250             `map_shift' (because with `shift', you get undef out if there was
251             nothing in the list).
252              
253             =cut
254              
255             sub mapcaru(&@)
256             {
257 1     1 1 3 my $sub= shift;
258 1 50       6 if( ! @_ ) {
259 0         0 croak( "mapcaru: nothing to map" );
260             }
261 1         2 my $max= 0;
262 1         3 for my $av ( @_ ) {
263 3 50       13 if( ! UNIVERSAL::isa( $av, "ARRAY" ) ) {
264 0         0 croak( "mapcaru: `$av' is not an array reference" );
265             }
266 3 100       10 $max = @$av if $max < @$av;
267             }
268 1         1 my @ret;
269 1         5 for( my $i= 0; $i < $max; $i++ ) {
270 5         22 push @ret, &$sub( map { $_->[$i] } @_ );
  15         33  
271             }
272 1 50       11 return wantarray ? @ret : \@ret;
273             }
274 1     1 1 629 sub map_shift(&@) { goto \&mapcaru }
275              
276             =item map_each { code } \%hash, \%hash, ...
277              
278             "map_each" is a version of `map' that works on hashes. B
279             work like mapcar or mapcaru, it is a simple map for hashes>.
280             Supplying multiple hashes iterates over all of the hashes in sequence.
281              
282             =cut
283              
284             sub map_each(&@)
285             {
286 1     1 1 7150 my $sub = shift;
287 1 50       9 if( ! @_ ) {
288 0         0 croak( "mapeach: Nothing to map" );
289             }
290 1 50       2 map { UNIVERSAL::isa($_, "HASH") or do {
  1         7  
291 0         0 croak( "mapeach: `$_' is not a hash reference" );
292             }; } @_;
293              
294 1         2 my @results;
295 1         1 while ( my @a = each %{$_[0]}) {
  5         35  
296 4         8 push @results, $sub->(@a);
297             }
298 1         5 return @results;
299             }
300              
301             =item imapcar [TODO] ...
302              
303             =item imap_for ...
304              
305             =item imap_foreach ...
306              
307             Returns an iterator version of mapcar (a CODE reference)
308              
309             =back
310              
311             =cut
312              
313             sub imapcar(&@) {
314 0     0 1 0 die "imapcar not yet implemented";
315             }
316              
317 0     0 1 0 sub imap_for (&@) { goto \&imapcar };
318 0     0 1 0 sub imap_foreach (&@) { goto \&imapcar };
319              
320             =head2 map's cousins
321              
322             While not as mapxy as our star, this group of functions will be found
323             alongside map and imap in many a code fragment.
324              
325             =over
326              
327             =item iter($iter, [ ], ...)
328              
329             This function simply returns an iterator that iterates over the input
330             list; it is exactly the same as:
331              
332             imap { $_ } (...)
333              
334             =cut
335              
336             sub iter {
337 18     18 1 2663 (my @__, @_) = @_;
338 18         31 my ($n, $i) = (0, undef);
339              
340             return bless sub {
341 74     74   102 my $rv;
342 74         258 while (!defined $rv) {
343             # set up the `next' iterator
344 92 100       235 unless (defined $i) {
345 50 100       152 return if $n > $#__;
346             $i = _adapt_iter($__[$n]) || sub {
347             $i = undef;
348             $__[$n++];
349 32   100     75 };
350             }
351             # iterate
352 74         153 $rv = ($i->());
353 74 100       169 if (defined $rv) {
354 56         164 return $rv;
355             } else {
356 18         21 $n++;
357 18         90 $i = undef;
358             }
359             }
360 18         742 }, __PACKAGE__;
361             }
362              
363 0     0 0 0 sub NEXT { $_[0]->() }
364 58     58   826 sub __next__ { $_[0]->() }
365 0     0 0 0 sub get_next { $_[0]->() }
366 0     0 0 0 sub next { $_[0]->() }
367 0     0 0 0 sub readline { $_[0]->() }
368              
369             =item slurp($iter, [ ], ...)
370              
371             This function is the opposite of iter; it takes iterators, gets them
372             to spit values out until they are finished (or all of VM runs out,
373             your machine starts swapping and eventually crashes, esp. on Linux).
374             See L.
375              
376             =cut
377              
378             sub slurp {
379 2     2 1 23 my @rv;
380              
381 2         8 for (my $n = 0; $n <= $#_; $n++) {
382 3 100       9 if (my $i = _adapt_iter($_[$n])) {
383 2         5 while (defined(my $item = $i->())) {
384 7         18 push @rv, $item;
385             }
386             } else {
387 1         4 push @rv, $_[$n];
388             }
389             }
390              
391 2         21 @rv;
392             }
393              
394             =item filter
395              
396             To save you from having to put unsightly `$_' at the end of your map
397             blocks, eg
398              
399             @a = ( filter { s{.*/(.*)}{} }
400             split /\0/,
401             `find . -type f -print0` );
402              
403             for (@a) {
404             # do something with each filename
405             }
406              
407             =cut
408              
409             sub filter(&@) {
410 0     0 1 0 my $sub = shift;
411 0         0 my @rv;
412 0         0 my @input = slurp @_;
413 0         0 while (@input) {
414 0         0 local($_) = shift @input;
415 0         0 $sub->();
416 0         0 push @rv, $_;
417             }
418 0         0 @rv;
419             }
420              
421             =item ifilter
422              
423             Of course the above is much better written iteratively:
424              
425             use IO::Handle;
426              
427             open FIND, "find . -type f -print0 |";
428             FIND->input_record_seperator("\0");
429              
430             $iter = ifilter { s{.*/(.*)}{} } \*FIND;
431              
432             while ( my $filename = $iter->() ) {
433             # do something with each filename
434             }
435              
436             =cut
437              
438             sub ifilter(&@) {
439 1     1 1 9 my $sub = shift;
440 1         3 my $iter = iter(@_);
441              
442             return bless sub {
443 6     6   38 my $val = $iter->();
444 6 100       19 if (defined($val)) {
445 5         13 local($_) = $val;
446 5         17 $sub->();
447 5         34 return $_;
448             } else {
449 1         5 return;
450             }
451 1         7 }, __PACKAGE__;
452             }
453              
454             =item igrep { BLOCK }, [...]
455              
456             Iterative `grep'
457              
458             =cut
459              
460             sub igrep(&@) {
461 1     1 1 20 my $sub = shift;
462 1         3 my $iter = iter @_;
463              
464             return bless sub {
465 4     4   18 my $ok = 0;
466 4         20 while (1) {
467 6         19 local($_) = $iter->();
468 6 100       16 return unless defined $_;
469 5 100       12 if ($sub->()) {
470 3         24 return $_;
471             }
472             }
473 1         6 }, __PACKAGE__;
474             }
475              
476             =for thought; isplit
477              
478             A version of `split' that uses a scalar context C loop to return
479             an iterator over a string.
480              
481             eg, here is a tokeniser that tokenizes a moronically small sub-set of
482             XML:
483              
484             my $iter = isplit qr/<[^>]*>|[^<]*/, $string;
485              
486             Each call to $iter->() would return the next tag or CDATA section of
487             the string, assuming that the input didn't come from the real world.
488             $1, $2, etc are available as per normal with this function; though if
489             the iterator is called in list context, they are returned as a list
490             (yay!).
491              
492             sub isplit($@) {
493             my $regex = shift;
494             my $iter = iter @_;
495             my ($string, $pos);
496             my $result = bless sub {
497             while (1) {
498             unless (defined $string) {
499             defined($string = $iter->()) or return;
500             }
501             if (defined (my $ok = ($string =~ m/$regex/g))) {
502             if (wantarray) {
503             # nasty! but only way to be sure...
504             return ($& =~ m/$regex/);
505             } else {
506             return $ok;
507             }
508             }
509             }
510             }, __PACKAGE__;
511              
512             return $result;
513             }
514              
515             =cut
516              
517              
518              
519             1;
520              
521             __END__