File Coverage

lib/Functional/Utility.pm
Criterion Covered Total %
statement 58 58 100.0
branch 18 22 81.8
condition 15 18 83.3
subroutine 18 18 100.0
pod 4 7 57.1
total 113 123 91.8


line stmt bran cond sub pod time code
1 5     5   235171 use strict;
  5         13  
  5         179  
2 5     5   30 use warnings;
  5         11  
  5         263  
3             package Functional::Utility;
4 5     5   30 use base qw(Exporter);
  5         13  
  5         825  
5              
6 5     5   5530 use Time::HiRes ();
  5         18907  
  5         4156  
7              
8             our @EXPORT_OK = qw(
9             context
10             hook_run_hook
11             hook_run
12             throttle
13             y_combinator
14             );
15              
16             # most of my modules start at 0.01. This one started at 1.01 because
17             # I actually use this code in production.
18             our $VERSION = 1.02;
19              
20             sub context {
21 11     11 0 1460 my ($lookback) = @_;
22 11   100     99 my $wa = (caller($lookback || 0))[5];
23 11 100       41 return 'VOID' unless defined $wa;
24 6 100       22 return 'SCALAR' if !$wa;
25 3 50       13 return 'LIST' if $wa;
26             }
27              
28             sub hook_run_hook {
29 6     6 1 11 my ($pre, $code, $post) = @_;
30              
31 6 50       28 $pre->() if $pre;
32              
33 6         245 my $callers_context = context(1);
34 6         9 my @ret;
35             +{
36 1     1   5 LIST => sub { @ret = $code->() },
37 1     1   4 SCALAR => sub { $ret[0] = $code->() },
38 4     4   10 VOID => sub { $code->(); return },
  4         1001070  
39 6         76 }->{$callers_context}->();
40              
41 6 50       2000893 $post->() if $post;
42              
43 6 100       170 return $callers_context eq 'LIST' ? @ret : $ret[0];
44             }
45              
46             sub hook_run {
47 3     3 1 12187 my (%args) = @_;
48 3         18 return hook_run_hook(@args{qw(before run after)});
49             }
50              
51             {
52             my ($delay_time, $nth_run);
53             sub throttle_delay (&$) {
54 3     3 0 11 my ($code, $delay) = @_;
55 3         26 my $delta = Time::HiRes::time - ($delay_time = Time::HiRes::time);
56 3 100 66     23 Time::HiRes::sleep($delay - $delta) if $nth_run && $delay - $delta > 0;
57 3   100     161 $nth_run ||= 1;
58 3         7 $code->();
59             }
60              
61             my ($ultimate_factor_duration, $penultimate_factor_duration);
62             sub throttle_factor (&$) {
63 3     3 0 8 my ($code, $factor) = @_;
64 3         4 my $start;
65             return hook_run_hook(
66             sub {
67             # If we're about to excute the 3rd or higher run, we can easily calculate how much we need to sleep
68             # so the delay between runs is the right $factor.
69 3   100 3   22 my $catchup = (($penultimate_factor_duration || 0) * $factor) - ($ultimate_factor_duration || 0);
      100        
70 3 100       9 Time::HiRes::sleep($catchup) if $catchup > 0;
71              
72             # Are we about to execute the 2nd run? If so, we should sleep a little before executing so the delay
73             # between the 1st and 2nd run is the right $factor.
74 3   100     34 my $whoa_there_nelly = defined $ultimate_factor_duration && ! defined $penultimate_factor_duration;
75              
76 3         5 $penultimate_factor_duration = $ultimate_factor_duration;
77              
78 3 100       6 if ($whoa_there_nelly) {
79 1   50     5 my $catchup = (($penultimate_factor_duration || 0) * $factor) - ($ultimate_factor_duration || 0);
      50        
80 1 50       6 Time::HiRes::sleep($catchup) if $catchup > 0;
81             }
82              
83 3         140 $start = Time::HiRes::time;
84             },
85             $code,
86             sub {
87 3     3   8 $ultimate_factor_duration = Time::HiRes::time - $start;
88             },
89 3         19 );
90             }
91              
92             sub throttle (&@) {
93 6     6 1 1503 my $type = splice @_, 1, 1;
94 6 100       24 goto &throttle_delay if $type eq 'delay';
95 3         13 goto &throttle_factor;
96             }
97             }
98              
99             sub y_combinator (&) {
100 1     1 1 1092 my $curried = shift;
101             return sub {
102 1     1   3 my $f1 = shift;
103 1         17 return $curried->(sub { $f1->($f1)(@_) })
104 1         6 }->(sub {
105 5     5   11 my $f2 = shift;
106 5         20 return $curried->(sub { $f2->($f2)(@_) });
  4         33  
107 1         9 });
108             }
109              
110             1;
111              
112             __END__