File Coverage

lib/ControlBreak.pm
Criterion Covered Total %
statement 126 126 100.0
branch 32 32 100.0
condition 5 6 100.0
subroutine 21 21 100.0
pod 12 12 100.0
total 196 197 100.0


line stmt bran cond sub pod time code
1             # ControlBreak.pm - Compare values during iteration to detect changes
2            
3             # Done:
4             # - change to use v5.26 to align with Object::Pad 0.66 requirements and avoid test failures
5            
6             # To Do:
7             # - provide an accumulate method that counts and sums an arbitrary number of named variables
8            
9            
10             ########################################################################
11             # perlcritic rules
12             ########################################################################
13            
14             ## no critic [ProhibitVersionStrings]
15             ## no critic [RequirePodAtEnd]
16            
17             =head1 NAME
18            
19             ControlBreak - Compare values during iteration to detect changes
20            
21             =head1 SYNOPSIS
22            
23             use v5.18;
24            
25             use ControlBreak;
26            
27             # set up two levels, in minor to major order
28             my $cb = ControlBreak->new( qw( District Country ) );
29            
30             my $country_total = 0;
31             my $district_total = 0;
32            
33             while (my $line = ) {
34             chomp $line;
35            
36             my ($country, $district, $city, $population) = split ',', $line;
37            
38             # test the values (minor to major order)
39             $cb->test($district, $country);
40            
41             # break on District (or Country) detected
42             if ($cb->break('District')) {
43             printf "%s,%s,%d%s\n", $cb->last('Country'), $cb->last('District'), $district_total, '*';
44             $district_total = 0;
45             }
46            
47             # break on Country detected
48             if ($cb->break('Country')) {
49             printf "%s total,%s,%d%s\n", $cb->last('Country'), '', $country_total, '**';
50             $country_total = 0;
51             }
52            
53             $country_total += $population;
54             $district_total += $population;
55             }
56             continue {
57             # save the current values (as received by ->test) as the new
58             # 'last' values on the next iteration.
59             $cb->continue();
60             }
61            
62             # simulate break at end of data, if we iterated at least once
63             if ($cb->iteration > 0) {
64             printf "%s,%s,%d%s\n", $cb->last('Country'), $cb->last('District'), $district_total, '*';
65             printf "%s total,%s,%d%s\n", $cb->last('Country'), '', $country_total, '**';
66             }
67            
68             __DATA__
69             Canada,Alberta,Calgary,1019942
70             Canada,Ontario,Ottawa,812129
71             Canada,Ontario,Toronto,2600000
72             Canada,Quebec,Montreal,1704694
73             Canada,Quebec,Quebec City,531902
74             Canada,Quebec,Sherbrooke,161323
75             USA,Arizona,Phoenix,1640641
76             USA,California,Los Angeles,3919973
77             USA,California,San Jose,1026700
78             USA,Illinois,Chicago,2756546
79             USA,New York,New York City,8930002
80             USA,New York,Buffalo,281757
81             USA,Pennsylvania,Philadelphia,1619355
82             USA,Texas,Houston,2345606
83            
84             =head1 DESCRIPTION
85            
86             The B module provides a class that is used to detect
87             control breaks; i.e. when a value changes.
88            
89             Typically, the data being retrieved or iterated over is ordered and
90             there may be more than one value that is of interest. For example
91             consider a table of population data with columns for country,
92             district and city, sorted by country and district. With this module
93             you can create an object that will detect changes in the district or
94             country, considered level 1 and level 2 respectively. The calling
95             program can take action, such as printing subtotals, whenever level
96             changes are detected.
97            
98             Ordered data is not a requirement. An example using unordered data
99             would be counting consecutive numbers within a data stream; e.g. 0 0
100             1 1 1 1 0 1 1. Using ControlBreak you can detect each change and
101             count the consecutive values, yielding two zeros, four 1's, one zero,
102             and two 1's.
103            
104             Note that ControlBreak cannot detect the end of your data stream.
105             The B method is normally called within a loop to detect changes
106             in control variables, but once the last iteration is processed there
107             are no further calls to B as the loop ends. It may be necessary,
108             therefore, to do additional processing after the loop in order to
109             handle the very last data group; e.g. to print a final set of subtotals.
110            
111             To simplify this situation, method B can be used in
112             place of B and B.
113            
114             =cut
115            
116             ########################################################################
117             # Libraries and Features
118             ########################################################################
119 11     11   706929 use strict;
  11         99  
  11         273  
120 11     11   47 use warnings;
  11         17  
  11         231  
121 11     11   93 use v5.26; # minimum perl necessary for Object::Pad 0.66
  11         39  
122            
123 11     11   5510 use Object::Pad 0.66 qw( :experimental(init_expr) );
  11         100387  
  11         43  
124            
125             package ControlBreak;
126             class ControlBreak;
127            
128             # althouth Object::Pad allows a version argument on the class statement
129             # we can't use it because we want Dist::Zilla to set it from the dist.ini
130             # version -- and that requires it to be an 'our' statement.
131             our $VERSION = 'v0.22.244';
132            
133 11     11   4344 use Carp qw(croak);
  11         19  
  11         28845  
134            
135             # public attributes
136 17     17 1 181 field $iteration :reader { 0 }; # [0] counts iterations
  17         38  
137 1     1 1 630 field @level_names :reader; # [1] list of level names
  1         4  
138            
139             # private attributes
140             field $_num_levels; # [2] the number of control levels
141             field %_levname { }; # [3] map of levidx to levname
142             field %_levidx { }; # [4] map of lenname to levidx
143             field %_comp_op; # [5] comparison operators
144             field %_fcomp; # [6] comparison functions
145             field $_test_levelnum { 0 }; # [7] last level returned by test()
146             field $_test_levelname { '' }; # [8] last level returned by test()
147             field @_test_values; # [9] the values of the current test()
148             field @_last_values; # [10] the values from the previous test()
149             field $_continue_count { 0 }; # [11] the number of types continue was called
150            
151             =head1 FIELDS
152            
153             =head2 iteration
154            
155             A readonly field that provides the current iteration number.
156            
157             This can be useful if you are doing an final processing after an
158             iteration loop has ended. In the event that the data stream is empty
159             and there were no iterations, then you can condition your final
160             processing on iteration > 0.
161            
162             Note that the B field is incremented by B (or
163             B). Therefore, when called within a loop it is
164             effectively zero-based if referenced within the iteration block
165             before B is invoked, and then one-based after B.
166            
167             =head2 level_names
168            
169             A readonly field that provides a list of the level names that were
170             provided as arguments to B.
171            
172             =cut
173            
174             ######################################################################
175             # Constructor (a.k.a. the new() method)
176             ######################################################################
177            
178             =head1 METHODS
179            
180             =head2 new ( $level_name> [, $level_name> ]... )
181            
182             Create a new ControlBreak object.
183            
184             Arguments are user-defined names for each level, in minor to major
185             order. The set of names must be unique, and they must each start
186             with a letter or underscore, followed by any number of letters,
187             numbers or underscores.
188            
189             A level name can also begin with a '+', which denotes that a numeric
190             comparison will be used for the values processed at this level.
191            
192             The number of arguments to B determines the number of control
193             levels that will be monitored. The variables provided to method
194             test() must match in number and datatype to these operators.
195            
196             The order of the arguments corresponds to a hierarchical level of
197             control, from lowest to highest; i.e. the first argument corresponds
198             to level 1, the second to level 2, etc. This also corresponds to
199             sort order, from minor to major, when iterating through a data stream.
200            
201             =cut
202            
203             BUILD {
204             croak '*E* at least one argument is required'
205             if @_ == 0;
206            
207             foreach my $arg (@_) {
208             croak '*E* invalid level name'
209             unless $arg =~ m{ \A [+]? [[:alpha:]_]\w+ }xms;
210             }
211            
212             $_num_levels = @_;
213            
214             my %lev_count;
215            
216             foreach my $arg (@_) {
217             $lev_count{$arg}++;
218             croak '*E* duplicate level name: ' . $arg
219             if $lev_count{$arg} > 1;
220             my $level_name = $arg;
221             my $is_numeric = $level_name =~ s{ \A [+] }{}xms;
222             push @level_names, $level_name;
223             my $op = $is_numeric ? '==' : 'eq';
224             $_comp_op{$level_name} = $op;
225             $_fcomp{$level_name} = _op_to_func($op);
226             }
227            
228             @_last_values = ( undef ) x $_num_levels;
229            
230             my $ii = 0;
231             map { $_levname{$ii++} = $_ } @level_names;
232            
233             $ii = 0;
234             map { $_levidx{$_} = $ii++ } @level_names;
235             }
236            
237             ######################################################################
238             # Public methods
239             ######################################################################
240            
241             =head2 break ( [ $level_name ] )
242            
243             The B method provides a convenient way to check whether the
244             last invocation of the test method resulted in a control break, or a
245             control break greater than or equal to the optionally
246             provided as an argument.
247            
248             For example, if you have levels 'City', 'State' and 'Country', and
249             there's a control break on level 1 (City), then invoking B
250             will return 1 and therefore be treated as true within a condition.
251             If there was no control break, then 0 (false) is returned.
252            
253             When invoked with a level name argument, B will map the
254             level name to a level number and compare it to the control break
255             level determined by the last invocation of test(). If the tested
256             control break level number is equal or higher than the argument
257             level, then that level number is returned and, since it will be
258             non-zero, treated as a true value within a condition. Otherwise,
259             zero (false) is returned.
260            
261             Ultimately the point of this is that you can use it to write a series
262             of actions, like printing subtotals and clearing subtotal variables,
263             such that a higher level control break will trigger actions
264             associated with lower level control breaks. For example:
265            
266             my $cb = ControlBreak( qw/City State Country/ );
267            
268             if ( $cb->break() ) {
269             say '=== control break detected at level: ' . $cb->levelname;
270             }
271             if ( $cb->break('City') ) {
272             say "City total: $city";
273             $city = 0;
274             }
275             if ( $cb->break('State') ) {
276             say "State total: $state";
277             $state = 0;
278             }
279             if ( $cb->break('Country') ) {
280             say "Country total: $country";
281             $country = 0;
282             }
283            
284             In this example, when a Country control break is detected all three
285             subtotals will be printed. When a State control break is detected,
286             only State and City will print.
287            
288             =cut
289            
290 200     200 1 711 method break ( $level_name=undef ) {
  200         215  
  200         215  
  200         197  
291 200 100       287 if ($level_name) {
292             croak '*E* invalid level name: ' . $level_name
293 167 100       270 unless exists $_levidx{$level_name};
294 166         189 my $levnum = $_levidx{$level_name} + 1;
295 166         304 return $_test_levelnum >= $levnum;
296             }
297            
298 33         163 return $_test_levelnum;
299             }
300            
301             =head2 comparison ( level_name => [ 'eq' | '==' | sub ] ... )
302            
303             The B method accepts a hash which sets the comparison
304             operations for the designated levels. Keywords must match the level
305             names provide in B. Values can be '==' for numeric comparison,
306             'eq' for alpha comparison, or anonymous subroutines.
307            
308             Anonymous subroutines must take two arguments, compare them in some
309             fashion, and return a boolean. The first argument to the comparison
310             routine will be the value passed to the B method. The second
311             argument will be the corresponding value from the last iteration.
312            
313             All levels are provided with default comparison functions as
314             determined by B. This method is provided so you can change
315             one or more of those defaults. Any level name not referenced by keys
316             in the argument list will be left unchanged.
317            
318             Some handy comparison functions are:
319            
320             # case-insensitive match
321             sub { lc $_[0] eq lc $_[1] }
322            
323             # strings coerced to numbers (so 07 and 7 are equal)
324             sub { ($_[0] + 0) == ($_[1] + 0) }
325            
326             # blank values treated as matched
327             sub { $_[0] eq '' ? 1 : $_[0] eq $_[1] }
328            
329             =cut
330            
331 7     7 1 1235 method comparison (%h) {
  7         10  
  7         17  
  7         10  
332 7         38 while ( my ($level_name, $v) = each %h ) {
333             croak '*E* invalid level name: ' . $level_name
334 8 100       39 unless exists $_levidx{$level_name};
335 6         12 $_comp_op{$level_name} = $v;
336 6         28 $_fcomp{$level_name} = _op_to_func($v);
337             }
338             }
339            
340             =head2 continue
341            
342             Saves the values most recently provided to the B method so
343             they can be compared to new values on the next iteration.
344            
345             On the next iteration these values will be accessible via the
346             B method.
347            
348             B is best invoked within the continue block of a loop, to
349             make sure it isn't missed.
350            
351             B cannot be used in conjunction with B,
352             which internally calls B and B for you.
353            
354             =cut
355            
356 144     144 1 12921 method continue () {
  144         174  
  144         139  
357 144         271 @_last_values = @_test_values;
358 144         342 $_continue_count++;
359             }
360            
361             =head2 last ( $level_name_or_number> )
362            
363             For the corresponding level, returns the value that was given to the
364             B method called prior to the most recent one.
365            
366             The argument can be a level name or a level number.
367            
368             Normally this is used while iterating through a data stream. When a
369             level change (i.e. control break) is detected, the current data value
370             has changed relative to the preceding iteration. At this point it
371             may be necessary to take some action, such a printing a subtotal.
372             But, the subtotal will be for the preceding group of data and the
373             current value belongs to the next group. The B method allows
374             you to access the value for the group that was just processed so, for
375             example, the group name can be included on the subtotal line.
376            
377             For example, if control levels were named 'X' and 'Y' and you are
378             iterating through data and invoking test($x, $y) at each iteration,
379             then invoking $cb->last('Y') on iteration 9 will returns the value of
380             $y on iteration 8.
381            
382             Note that B should not be invoked before B within
383             the scope of an iteration loop; i.e. B should be the last
384             thing done before the next turn of the loop.
385            
386             =cut
387            
388 90     90 1 1430 method last ($arg) { ## no critic [ProhibitParensWithBuiltins]
  90         93  
  90         103  
  90         91  
389 90         102 my $retval;
390            
391 90 100       201 if ( $arg =~ m{ \A [1-9]\d* \Z }xms ) {
392 3         6 my $levidx = $arg - 1;
393             croak '*E* invalid level number: ' . $arg
394 3 100       25 unless exists $_levname{$levidx};
395 1         4 $retval = $_last_values[$levidx];
396             } else {
397             croak '*E* invalid level name: ' . $arg
398 87 100       152 unless exists $_levidx{$arg};
399 86         126 $retval = $_last_values[$_levidx{$arg}];
400             }
401            
402 87         942 return $retval;
403             }
404            
405             =head2 levelname
406            
407             Return the level name for the most recent invocation of the B
408             method.
409            
410             =cut
411            
412 1     1 1 5 method levelname () {
  1         2  
  1         2  
413 1         4 return $_test_levelname;
414             }
415            
416             =head2 levelnum
417            
418             Return the level number for the most recent invocation of the
419             B method.
420            
421             =cut
422            
423 86     86 1 300 method levelnum () {
  86         86  
  86         86  
424 86         132 return $_test_levelnum;
425             }
426            
427            
428             =head2 level_numbers
429            
430             Return a list of level numbers corresponding to the levels defined
431             in B. This can be useful, for example, when you want to
432             set up some lexical variables for use as indexes into a list you
433             might use to accumulate subtotals.
434            
435             my $cb = ControlBreak->new( qw( L1 L2 EOD ) );
436             my @totals;
437             my ($L1, $L2, $EOD) = $cb->level_numbers;
438            
439             foreach my $sublist (@list_of_lists) {
440             my ($control1, $control2, $number) = $sublist->@*;
441             ...
442             my $sub_totals = sub {
443             if ($cb->break('L1')) {
444             # report the L1 subtotal here
445             $totals[$L1] = 0; # clear the subtotal
446             }
447             ...
448             # accumulate subtotals
449             map { $totals[$_] += $number } $cb->level_numbers;
450             };
451            
452             $cb->test_and_do(
453             $control1,
454             $control2,
455             $cb->iteration == $list_of_lists - 1,
456             $sub_totals
457             );
458             }
459            
460            
461             =cut
462            
463 32     32 1 125 method level_numbers () {
  32         33  
  32         32  
464 32         70 return 1 .. $_num_levels;
465             }
466            
467             =head2 reset
468            
469             Resets the state of the object so it can be used again for another
470             set of iterations using the same number and type of controls
471             establish when the object was instantiated with B. Any
472             comparisons that were subsequently modified are retained.
473            
474             =cut
475            
476 2     2 1 551 method reset () { ## no critic [ProhibitParensWithBuiltins]
  2         3  
  2         4  
477 2         3 $iteration = 0;
478 2         4 $_continue_count = 0;
479 2         3 $_test_levelnum = 0;
480 2         4 $_test_levelname = 0;
481 2         3 @_test_values = ();
482 2         7 @_last_values = ( undef ) x $_num_levels;
483             }
484            
485             =head2 test ( $var1 [, $var2 ]... )
486            
487             Submits the control variables for testing against the values from the
488             previous iteration.
489            
490             Testing is done in reverse order, from highest to lowest (major to
491             minor) and stops once a change is detected. Where it stops determines
492             the control break level. For example, if $var2 changed, method
493             levelnum will return 2. If $var2 did not change, but $var1 did, then
494             method B will return 1. If nothing changes, then
495             B will return 0.
496            
497             Note that the level numbers set by B are true if there was
498             a level change, and false if there wasn't. So, they can be used as a
499             simple boolean test of whether there was a change. Or you can use
500             the B method to determine whether any control break has
501             occurred.
502            
503             Because level numbers correspond to the hierarchical data order, they
504             can be use to trigger multiple actions; e.g. B >= 1 could
505             be used to print subtotals for levels 1 whenever a control break
506             occurred for level 1, 2 or 3. It is usually the case that higher
507             control breaks are meant to cascade to lower control levels and this
508             can be achieved in this fashion. The B method simplifies
509             this.
510            
511             Note that method B must be called at the end of each
512             iteration in order to save the values of the iteration for the next
513             iteration. If not, the next B invocation will croak.
514            
515             =cut
516            
517 151     151 1 4401 method test (@args) {
  151         165  
  151         254  
  151         156  
518 151 100       296 croak '*E* number of arguments to test() must match those given in new()'
519             if @args != $_num_levels;
520            
521 149 100       283 croak '*E* continue() must be called after test()'
522             unless $iteration == $_continue_count;
523            
524 148         232 @_test_values = @args;
525            
526 148         172 $iteration++;
527            
528 148         152 my $is_break;
529 148         160 my $lev_idx = 0;
530            
531             # process tests in reverse order of arguments; i.e. major to minor
532 148         177 my $jj = @args;
533 148         218 foreach my $arg (reverse @args) {
534 257         282 $jj--;
535            
536             # on the first iteration, make the last values match the current
537             # ones so we don't detect any control break
538            
539 257 100 66     442 $_last_values[$jj] //= $arg # uncoverable condition left
540             if $iteration == 1;
541            
542 257         346 my $level_name = $_levname{$jj};
543            
544             # compare the current and last values using the comparison function
545             # if they don't match, then it's a control break
546 257         448 $is_break = not $_fcomp{$level_name}->( $arg, $_last_values[$jj] );
547            
548 257 100       587 if ( $is_break ) {
549             # internally our lists use the usual zero-based indexing
550             # but externally our level numbers are 1-based, where
551             # 1 is the most minor control variable. Level 0 is used
552             # to denote no level; i.e. no control break. Since zero
553             # is treated as false by perl, and non-zero as true, we
554             # can use the level number in a condition to determine if
555             # there's been a control break; ie. $level ? 'break' : 'no break'
556 74         87 $lev_idx = $jj + 1;
557 74         106 last;
558             }
559             }
560 148         177 my $lev_num = $lev_idx;
561            
562 148         155 $_test_levelnum = $lev_num;
563 148         188 $_test_levelname = $_levname{$jj};
564            
565 148         271 return;
566             }
567            
568             =head2 test_and_do ( $var1 [, $var2 ]... $var_end, $coderef )
569            
570             The B method is similar to B. It takes the same
571             arguments as B, plus one additional argument that is an
572             anonymous code reference. Internally, it calls B and then, if
573             there is a control break, calls the anonymous subroutine provided in
574             the last argument. Typically, that code will perform work related to
575             subtotals or other actions necessary when a control break occurs.
576            
577             But B does one other thing. It expects the last
578             control variable ($var_end) to be an end of data indicator, such as
579             the perl builtin operator B. This indicator should return false
580             on each iteration over the data until the very last iteration -- when
581             it should change to true, thereby triggering a major control break.
582            
583             What test_and_do does then is to add an extra loop. This simulates
584             a final record and will trigger B to signal control breaks
585             at all levels. Thus, the code provided will be executed between
586             every change of data AND after all data has been iterated over.
587            
588             This avoids the necessity of repeating the control break actions
589             you've put inside the data loop immediately after the loop's closing
590             bracket. When you just use B and B, an end-of-data
591             control break won't occur and the simplest workaround is to just
592             duplicate your control break code after the loops closing bracket.
593            
594             Here's a typical use case involving end of file processing. Note the
595             extra control level, named 'EOF', and the use of the B builtin
596             function as the second last argument of B:
597            
598             my $cb = ControlBreak->new( qw( L1 L2 EOF ) );
599            
600             my $lev1_subtotal = 0;
601             my $lev2_subtotal = 0;
602             my $grand_total = 0;
603            
604             while (my $line = <>) {
605             chomp $line;
606            
607             my ($lev1, $lev2, $data) = split "\t", $line;
608            
609             my $subtotal_coderef = sub {
610             if ($cb->break('L1')) {
611             say $cb->last('L1'), $cb->last('L2'), $lev1_subtotal . '*';
612             $lev1_subtotal = 0;
613             }
614             ...
615             if ($cb->break('EOF')) {
616             say 'Grand total,,', $grand_total, '***';
617             }
618            
619             $lev1_subtotal += $data;
620             $lev2_subtotal += $data;
621             $gran_total += $data;
622             }
623            
624             $cb->test_and_do($lev1, $lev2, eof, $subtotal_coderef);
625             }
626            
627             Also note that if your subroutine needs to reference variables
628             defined outside the scope of the loop (as in this case with the
629             totalling variables) then it needs to be defined within the loop so
630             it can be a closure over the variables in the enclosing scope.
631            
632             Another typical use case involves iterating over a list of values.
633             Here, we have no built in function to tell us when we've reached the
634             final value, but if we have a fixed list of values we can use the
635             length of the list and test it against the value returned by the
636             ControlBreak iterator method. For example:
637            
638             my $cb = ControlBreak->new( qw( L1 L2 EOD ) );
639            
640             my $lev1_subtotal = 0;
641             my $lev2_subtotal = 0;
642             my $grand_total = 0;
643            
644             my $last_iter = @data - 1;
645            
646             foreach my $line (@data {
647             chomp $line;
648             my ($lev1, $lev2, $data) = split "\t", $line;
649            
650             my $subtotal_coderef = sub {
651             if ($cb->break('L1')) {
652             say $cb->last('L1'), $cb->last('L2'), $lev1_subtotal . '*';
653             $lev1_subtotal = 0;
654             }
655             ...
656             if ($cb->break('EOD')) {
657             say 'Grand total,,', $grand_total, '***';
658             }
659            
660             $lev1_subtotal += $data;
661             $lev2_subtotal += $data;
662             $gran_total += $data;
663             }
664            
665             $cb->test_and_do($lev1, $lev2, $cb->iteration == $last_iter, $subtotal_coderef);
666             }
667            
668             =cut
669            
670 33     33 1 296 method test_and_do (@args) {
  33         37  
  33         61  
  33         39  
671            
672 33 100       73 croak '*E* test_and_do must have one more argument than new()'
673             unless @args == $_num_levels + 1;
674            
675 32         45 my $coderef = pop @args;
676            
677 32 100       64 croak '*E* test_and_do last argument must be a code reference'
678             unless ref $coderef eq 'CODE';
679            
680 31         36 my $eod = $args[-1];
681            
682 31 100 100     114 croak '*E* test_and_do invalid boolean value in 2nd-last argument: ' . $eod
683             if $eod and $eod !~ m{ \A [01] \Z }xms;
684            
685 29         48 for my $ii (0..$eod) {
686 32         38 $args[-1] = $ii;
687 32         66 $self->test(@args);
688 32         60 $coderef->();
689 32         172 $self->continue;
690             }
691            
692 29         47 return $self->break;
693             }
694            
695             ######################################################################
696             # Private subroutines and functions
697             ######################################################################
698 39     39   48 sub _op_to_func ($op) {
  39         53  
  39         43  
699            
700 39         43 my $fcompare;
701            
702 11     11   90 no warnings 'uninitialized';
  11         20  
  11         3627  
703            
704 39 100       104 if ($op eq '==') {
    100          
    100          
705 6     37   21 $fcompare = sub { $_[0] == $_[1] };
  37         72  
706             }
707             elsif ($op eq 'eq') {
708 28     185   92 $fcompare = sub { $_[0] eq $_[1] };
  185         313  
709             }
710             elsif (ref $op eq 'CODE') {
711 4         7 $fcompare = $op;
712             }
713             else {
714 1         9 croak '*E* invalid comparison operator: ' . $op;
715             }
716            
717 38         121 return $fcompare;
718             }
719            
720             1;
721            
722             __END__