File Coverage

blib/lib/List/Keywords.pm
Criterion Covered Total %
statement 34 36 94.4
branch 16 22 72.7
condition n/a
subroutine 6 6 100.0
pod 0 2 0.0
total 56 66 84.8


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2021 -- leonerd@leonerd.org.uk
5              
6             package List::Keywords 0.09;
7              
8 10     10   2239384 use v5.14;
  10         90  
9 10     10   58 use warnings;
  10         21  
  10         265  
10              
11 10     10   56 use Carp;
  10         19  
  10         5501  
12              
13             require XSLoader;
14             XSLoader::load( __PACKAGE__, our $VERSION );
15              
16             =head1 NAME
17              
18             C - a selection of list utility keywords
19              
20             =head1 SYNOPSIS
21              
22             use List::Keywords 'any';
23              
24             my @boxes = ...;
25              
26             if( any { $_->size > 100 } @boxes ) {
27             say "There are some large boxes here";
28             }
29              
30             =head1 DESCRIPTION
31              
32             This module provides keywords that behave (almost) identically to familiar
33             functions from L, but implemented as keyword plugins instead of
34             functions. As a result these run more efficiently, especially in small code
35             cases.
36              
37             =head2 Blocks vs Anonymous Subs
38              
39             In the description above the word "almost" refers to the fact that as this
40             module provides true keywords, the code blocks to them can be parsed as true
41             blocks rather than anonymous functions. As a result, both C and
42             C will behave rather differently here.
43              
44             For example,
45              
46             use List::Keywords 'any';
47              
48             sub func {
49             any { say "My caller is ", caller; return "ret" } 1, 2, 3;
50             say "This is never printed";
51             }
52              
53             Here, the C will see C as its caller, and the C
54             statement makes the entire containing function return, so the second line is
55             never printed. The same example written using C will instead print
56             the C function as being the caller, before making just that
57             one item return the value, then the message on the second line is printed as
58             normal.
59              
60             In regular operation where the code is just performing some test on each item,
61             and does not make use of C or C, this should not cause any
62             noticable differences.
63              
64             =head2 Performance
65              
66             The following example demonstrates a simple case and shows how the performance
67             differs.
68              
69             my @nums = (1 .. 100);
70              
71             my $ret = any { $_ > 50 } @nums;
72              
73             When run for 5 seconds each, the following results were obtained on my
74             machine:
75              
76             List::Util::any 648083/s
77             List::Keyword/any 816135/s
78              
79             The C version here ran 26% faster.
80              
81             =cut
82              
83             my %KEYWORD_OK = map { $_ => 1 } qw(
84             first any all none notall
85             reduce reductions
86             );
87              
88             sub import
89             {
90 10     10   75 shift;
91 10         25 my @syms = @_;
92              
93 10         1787 foreach ( @syms ) {
94 16 100       54 if( $_ eq ":all" ) {
95 1         5 push @syms, keys %KEYWORD_OK;
96 1         3 next;
97             }
98              
99 15 50       64 $KEYWORD_OK{$_} or croak "Unrecognised import symbol '$_'";
100              
101 15         14651 $^H{"List::Keywords/$_"}++;
102             }
103             }
104              
105             sub B::Deparse::pp_firstwhile
106             {
107 6     6 0 12143 my ($self, $op, $cx) = @_;
108             # first, any, all, none, notall
109 6         24 my $private = $op->private;
110 6 50       35 my $name =
    100          
    100          
    100          
    100          
111             ( $private == 0 ) ? "first" :
112             ( $private == 6 ) ? "none" :
113             ( $private == 9 ) ? "any" :
114             ( $private == 22 ) ? "all" :
115             ( $private == 25 ) ? "notall" :
116             "firstwhile[op_private=$private]";
117              
118             # We can't just call B::Deparse::mapop because of the possibility of `my $var`
119             # So we'll inline it here
120 6         26 my $kid = $op->first;
121 6         28 $kid = $kid->first->sibling; # skip PUSHMARK
122 6         21 my $code = $kid->first;
123 6         18 $kid = $kid->sibling;
124 6 50       37 if(B::Deparse::is_scope $code) {
125 6         1731 $code = "{" . $self->deparse($code, 0) . "} ";
126 6 100       43 if($op->targ) {
127 1         27 my $varname = $self->padname($op->targ);
128 1         8 $code = "my $varname $code";
129             }
130             }
131             else {
132 0         0 $code = $self->deparse($code, 24);
133 0 0       0 $code .= ", " if !B::Deparse::null($kid);
134             }
135 6         11 my @exprs;
136 6         48 for (; !B::Deparse::null($kid); $kid = $kid->sibling) {
137 6         646 my $expr = $self->deparse($kid, 6);
138 6 50       80 push @exprs, $expr if defined $expr;
139             }
140 6         650 return $self->maybe_parens_func($name, $code . join(" ", @exprs), $cx, 5);
141             }
142              
143             sub B::Deparse::pp_reducewhile
144             {
145 1     1 0 1591 return B::Deparse::mapop(@_, "reduce");
146             }
147              
148             =head1 KEYWORDS
149              
150             =cut
151              
152             =head2 first
153              
154             $val = first { CODE } LIST
155              
156             I
157              
158             Repeatedly calls the block of code, with C<$_> locally set to successive
159             values from the given list. Returns the value and stops at the first item to
160             make the block yield a true value. If no such item exists, returns C.
161              
162             $val = first my $var { CODE } LIST
163              
164             I
165              
166             Optionally the code block can be prefixed with a lexical variable declaration.
167             In this case, that variable will contain each value from the list, and the
168             global C<$_> will remain untouched.
169              
170             =head2 any
171              
172             $bool = any { CODE } LIST
173              
174             Repeatedly calls the block of code, with C<$_> locally set to successive
175             values from the given list. Returns true and stops at the first item to make
176             the block yield a true value. If no such item exists, returns false.
177              
178             $val = any my $var { CODE } LIST
179              
180             I
181              
182             Uses the lexical variable instead of global C<$_>, similar to L.
183              
184             =head2 all
185              
186             $bool = all { CODE } LIST
187              
188             Repeatedly calls the block of code, with C<$_> locally set to successive
189             values from the given list. Returns false and stops at the first item to make
190             the block yield a false value. If no such item exists, returns true.
191              
192             $val = all my $var { CODE } LIST
193              
194             I
195              
196             Uses the lexical variable instead of global C<$_>, similar to L.
197              
198             =head2 none
199              
200             =head2 notall
201              
202             $bool = none { CODE } LIST
203             $bool = notall { CODE } LISt
204              
205             I
206              
207             Same as L and L but with the return value inverted.
208              
209             $val = none my $var { CODE } LIST
210             $val = notall my $var { CODE } LIST
211              
212             I
213              
214             Uses the lexical variable instead of global C<$_>, similar to L.
215              
216             =cut
217              
218             =head2 reduce
219              
220             $final = reduce { CODE } INITIAL, LIST
221              
222             I
223              
224             Repeatedly calls a block of code, using the C<$a> package lexical as an
225             accumulator and setting C<$b> to each successive value from the list in turn.
226             The first value of the list sets the initial value of the accumulator, and
227             each returned result from the code block gives its new value. The final value
228             of the accumulator is returned.
229              
230             =head2 reductions
231              
232             @partials = reductions { CODE } INITIAL, LIST
233              
234             I
235              
236             Similar to C, but returns a full list of all the partial results of
237             every invocation, beginning with the initial value itself and ending with the
238             final result.
239              
240             =cut
241              
242             =head1 TODO
243              
244             More functions from C:
245              
246             pairfirst pairgrep pairmap
247              
248             Maybe also consider some from L.
249              
250             =head1 ACKNOWLEDGEMENTS
251              
252             With thanks to Matthew Horsfall (alh) for much assistance with performance
253             optimizations.
254              
255             =cut
256              
257             =head1 AUTHOR
258              
259             Paul Evans
260              
261             =cut
262              
263             0x55AA;