File Coverage

blib/lib/Future/Utils.pm
Criterion Covered Total %
statement 157 161 97.5
branch 54 58 93.1
condition 37 55 67.2
subroutine 31 32 96.8
pod 7 9 77.7
total 286 315 90.7


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, 2013-2024 -- leonerd@leonerd.org.uk
5              
6             package Future::Utils 0.52;
7              
8 8     8   5144 use v5.14;
  8         32  
9 8     8   49 use warnings;
  8         16  
  8         555  
10              
11 8     8   53 use Exporter 'import';
  8         16  
  8         1271  
12             # Can't import the one from Exporter as it relies on package inheritance
13             sub export_to_level
14             {
15 0     0 0 0 my $pkg = shift; local $Exporter::ExportLevel = 1 + shift; $pkg->import(@_);
  0         0  
  0         0  
16             }
17              
18             our @EXPORT_OK = qw(
19             call
20             call_with_escape
21              
22             repeat
23             try_repeat try_repeat_until_success
24             repeat_until_success
25              
26             fmap fmap_concat
27             fmap1 fmap_scalar
28             fmap0 fmap_void
29             );
30              
31 8     8   59 use Carp;
  8         32  
  8         910  
32             our @CARP_NOT = qw( Future );
33              
34 8     8   52 use Future;
  8         14  
  8         20541  
35              
36             =head1 NAME
37              
38             C - utility functions for working with C objects
39              
40             =head1 SYNOPSIS
41              
42             =for highlighter language=perl
43              
44             use Future::Utils qw( call_with_escape );
45              
46             my $result_f = call_with_escape {
47             my $escape_f = shift;
48             my $f = ...
49             $escape_f->done( "immediate result" );
50             ...
51             };
52              
53             Z<>
54              
55             use Future::Utils qw( repeat try_repeat try_repeat_until_success );
56              
57             my $eventual_f = repeat {
58             my $trial_f = ...
59             return $trial_f;
60             } while => sub { my $f = shift; return want_more($f) };
61              
62             my $eventual_f = repeat {
63             ...
64             return $trial_f;
65             } until => sub { my $f = shift; return acceptable($f) };
66              
67             my $eventual_f = repeat {
68             my $item = shift;
69             ...
70             return $trial_f;
71             } foreach => \@items;
72              
73             my $eventual_f = try_repeat {
74             my $trial_f = ...
75             return $trial_f;
76             } while => sub { ... };
77              
78             my $eventual_f = try_repeat_until_success {
79             ...
80             return $trial_f;
81             };
82              
83             my $eventual_f = try_repeat_until_success {
84             my $item = shift;
85             ...
86             return $trial_f;
87             } foreach => \@items;
88              
89             Z<>
90              
91             use Future::Utils qw( fmap_concat fmap_scalar fmap_void );
92              
93             my $result_f = fmap_concat {
94             my $item = shift;
95             ...
96             return $item_f;
97             } foreach => \@items, concurrent => 4;
98              
99             my $result_f = fmap_scalar {
100             my $item = shift;
101             ...
102             return $item_f;
103             } foreach => \@items, concurrent => 8;
104              
105             my $done_f = fmap_void {
106             my $item = shift;
107             ...
108             return $item_f;
109             } foreach => \@items, concurrent => 10;
110              
111             =head1 DESCRIPTION
112              
113             This module provides a number of utility functions for working with L
114             instances, that cannot generally be provided as methods on the C class
115             itself (mostly as they are prototyped to take a block of code as the first
116             argument).
117              
118             Unless otherwise noted, the following functions require at least version
119             I<0.08>.
120              
121             =cut
122              
123             =head1 INVOKING A BLOCK OF CODE
124              
125             =head2 call
126              
127             $f = call { CODE };
128              
129             I
130              
131             The C function invokes a block of code that returns a future, and simply
132             returns the future it returned. The code is wrapped in an C block, so
133             that if it throws an exception this is turned into an immediate failed
134             C. If the code does not return a C, then an immediate failed
135             C instead.
136              
137             (This is equivalent to using C<< Future->call >>, but is duplicated here for
138             completeness).
139              
140             =cut
141              
142             sub call(&)
143             {
144 3     3 1 247045 my ( $code ) = @_;
145 3         49 return Future->call( $code );
146             }
147              
148             =head2 call_with_escape
149              
150             $f = call_with_escape { CODE };
151              
152             I
153              
154             The C function invokes a block of code that returns a
155             future, and passes in a separate future (called here an "escape future").
156             Normally this is equivalent to the simple C function. However, if the
157             code captures this future and completes it by calling C or C on
158             it, the future returned by C immediately completes with this
159             result, and the future returned by the code itself is cancelled.
160              
161             This can be used to implement short-circuit return from an iterating loop or
162             complex sequence of code, or immediate fail that bypasses failure handling
163             logic in the code itself, or several other code patterns.
164              
165             $f = $code->( $escape_f );
166              
167             (This can be considered similar to C as found
168             in some Scheme implementations).
169              
170             =cut
171              
172             sub call_with_escape(&)
173             {
174 4     4 1 209924 my ( $code ) = @_;
175              
176 4         23 my $escape_f = Future->new;
177              
178 4         21 return Future->wait_any(
179             Future->call( $code, $escape_f ),
180             $escape_f,
181             );
182             }
183              
184             =head1 REPEATING A BLOCK OF CODE
185              
186             The C function provides a way to repeatedly call a block of code that
187             returns a L (called here a "trial future") until some ending condition
188             is satisfied. The C function itself returns a C to represent
189             running the repeating loop until that end condition (called here the "eventual
190             future"). The first time the code block is called, it is passed no arguments,
191             and each subsequent invocation is passed the previous trial future.
192              
193             The result of the eventual future is the result of the last trial future.
194              
195             If the eventual future is cancelled, the latest trial future will be
196             cancelled.
197              
198             If some specific subclass or instance of C is required as the return
199             value, it can be passed as the C argument. Otherwise the return value
200             will be constructed by cloning the first non-immediate trial C.
201              
202             =head2 repeat+while
203              
204             $future = repeat { CODE } while => CODE;
205              
206             Repeatedly calls the C block while the C condition returns a true
207             value. Each time the trial future completes, the C condition is passed
208             the trial future.
209              
210             $trial_f = $code->( $previous_trial_f );
211             $again = $while->( $trial_f );
212              
213             If the C<$code> block dies entirely and throws an exception, this will be
214             caught and considered as an immediately-failed C with the exception as
215             the future's failure. The exception will not be propagated to the caller.
216              
217             =head2 repeat+until
218              
219             $future = repeat { CODE } until => CODE;
220              
221             Repeatedly calls the C block until the C condition returns a true
222             value. Each time the trial future completes, the C condition is passed
223             the trial future.
224              
225             $trial_f = $code->( $previous_trial_f );
226             $accept = $until->( $trial_f );
227              
228             =head2 repeat+foreach
229              
230             $future = repeat { CODE } foreach => ARRAY, otherwise => CODE;
231              
232             I
233              
234             Calls the C block once for each value obtained from the array, passing
235             in the value as the first argument (before the previous trial future). When
236             there are no more items left in the array, the C code is invoked
237             once and passed the last trial future, if there was one, or C if the
238             list was originally empty. The result of the eventual future will be the
239             result of the future returned from C.
240              
241             The referenced array may be modified by this operation.
242              
243             $trial_f = $code->( $item, $previous_trial_f );
244             $final_f = $otherwise->( $last_trial_f );
245              
246             The C code is optional; if not supplied then the result of the
247             eventual future will simply be that of the last trial. If there was no trial,
248             because the C list was already empty, then an immediate successful
249             future with an empty result is returned.
250              
251             =head2 repeat+foreach+while
252              
253             $future = repeat { CODE } foreach => ARRAY, while => CODE, ...;
254              
255             I
256              
257             =head2 repeat+foreach+until
258              
259             $future = repeat { CODE } foreach => ARRAY, until => CODE, ...;
260              
261             I
262              
263             Combines the effects of C with C or C. Calls the
264             C block once for each value obtained from the array, until the array is
265             exhausted or the given ending condition is satisfied.
266              
267             If a C or C condition is combined with C, the
268             C code will only be run if the array was entirely exhausted. If the
269             operation is terminated early due to the C or C condition being
270             satisfied, the eventual result will simply be that of the last trial that was
271             executed.
272              
273             =head2 repeat+generate
274              
275             $future = repeat { CODE } generate => CODE, otherwise => CODE;
276              
277             I
278              
279             Calls the C block once for each value obtained from the generator code,
280             passing in the value as the first argument (before the previous trial future).
281             When the generator returns an empty list, the C code is invoked and
282             passed the last trial future, if there was one, otherwise C if the
283             generator never returned a value. The result of the eventual future will be
284             the result of the future returned from C.
285              
286             $trial_f = $code->( $item, $previous_trial_f );
287             $final_f = $otherwise->( $last_trial_f );
288              
289             ( $item ) = $generate->();
290              
291             The generator is called in list context but should return only one item per
292             call. Subsequent values will be ignored. When it has no more items to return
293             it should return an empty list.
294              
295             For backward compatibility this function will allow a C or C
296             condition that requests a failure be repeated, but it will print a warning if
297             it has to do that. To apply repeating behaviour that can catch and retry
298             failures, use C instead. This old behaviour is now deprecated and
299             will be removed in the next version.
300              
301             =cut
302              
303             sub _repeat
304             {
305 36     36   166 my ( $code, $return, $trialp, $cond, $sense, $is_try ) = @_;
306              
307 36         70 my $prev = $$trialp;
308              
309 36         72 while(1) {
310 64   66     411 my $trial = $$trialp ||= Future->call( $code, $prev );
311 64         138 $prev = $trial;
312              
313 64 100       200 if( !$trial->is_ready ) {
314             # defer
315 13   66     47 $return ||= $trial->new;
316             $trial->on_ready( sub {
317 12 100   12   53 return if $$trialp->is_cancelled;
318 11         31 _repeat( $code, $return, $trialp, $cond, $sense, $is_try );
319 13         135 });
320 13         93 return $return;
321             }
322              
323 51         76 my $stop;
324 51 100       86 if( not eval { $stop = !$cond->( $trial ) ^ $sense; 1 } ) {
  51         106  
  50         152  
325 1   33     14 $return ||= $trial->new;
326 1         4 $return->fail( $@ );
327 1         3 return $return;
328             }
329              
330 50 100       109 if( $stop ) {
331             # Return result
332 22   66     79 $return ||= $trial->new;
333 22         103 $trial->on_done( $return );
334 22         88 $trial->on_fail( $return );
335 22         110 return $return;
336             }
337              
338 28 100 100     187 if( !$is_try and $trial->failure ) {
339 2         482 carp "Using Future::Utils::repeat to retry a failure is deprecated; use try_repeat instead";
340             }
341              
342             # redo
343 28         71 undef $$trialp;
344             }
345             }
346              
347             sub repeat(&@)
348             {
349 25     25 0 796577 my $code = shift;
350 25         98 my %args = @_;
351              
352             # This makes it easier to account for other conditions
353             defined($args{while}) + defined($args{until}) == 1
354             or defined($args{foreach})
355             or defined($args{generate})
356 25 50 100     153 or croak "Expected one of 'while', 'until', 'foreach' or 'generate'";
      66        
357              
358 25 100       69 if( $args{foreach} ) {
359 11 50       32 $args{generate} and croak "Cannot use both 'foreach' and 'generate'";
360              
361 11         21 my $array = delete $args{foreach};
362             $args{generate} = sub {
363 24 100   24   79 @$array ? shift @$array : ();
364 11         48 };
365             }
366              
367 25 100       70 if( $args{generate} ) {
368 14         24 my $generator = delete $args{generate};
369 14         26 my $otherwise = delete $args{otherwise};
370              
371             # TODO: This is slightly messy as this lexical is captured by both
372             # blocks of code. Can we do better somehow?
373 14         23 my $done;
374              
375 14         21 my $orig_code = $code;
376             $code = sub {
377 33     33   63 my ( $last_trial_f ) = @_;
378 33         70 my $again = my ( $value ) = $generator->( $last_trial_f );
379              
380 33 100       133 if( $again ) {
381 23         54 unshift @_, $value; goto &$orig_code;
  23         87  
382             }
383              
384 10         17 $done++;
385 10 100       23 if( $otherwise ) {
386 6         23 goto &$otherwise;
387             }
388             else {
389 4   66     18 return $last_trial_f || Future->done;
390             }
391 14         57 };
392              
393 14 100       50 if( my $orig_while = delete $args{while} ) {
    100          
394             $args{while} = sub {
395 6 100   6   19 $orig_while->( $_[0] ) and !$done;
396 3         10 };
397             }
398             elsif( my $orig_until = delete $args{until} ) {
399             $args{while} = sub {
400 2   66 2   9 !$orig_until->( $_[0] ) and !$done;
401 1         51 };
402             }
403             else {
404 10     25   35 $args{while} = sub { !$done };
  25         55  
405             }
406             }
407              
408 25         42 my $future = $args{return};
409              
410 25         46 my $trial;
411 25 100       122 $args{while} and $future = _repeat( $code, $future, \$trial, $args{while}, 0, $args{try} );
412 25 100       126 $args{until} and $future = _repeat( $code, $future, \$trial, $args{until}, 1, $args{try} );
413              
414 25     1   215 $future->on_cancel( sub { $trial->cancel } );
  1         5  
415              
416 25         240 return $future;
417             }
418              
419             =head2 try_repeat
420              
421             $future = try_repeat { CODE } ...;
422              
423             I
424              
425             A variant of C that doesn't warn when the trial fails and the
426             condition code asks for it to be repeated.
427              
428             In some later version the C function will be changed so that if a
429             trial future fails, then the eventual future will immediately fail as well,
430             making its semantics a little closer to that of a C loop in Perl.
431             Code that specifically wishes to catch failures in trial futures and retry
432             the block should use C specifically.
433              
434             =cut
435              
436             sub try_repeat(&@)
437             {
438             # defeat prototype
439 6     6 1 3350 &repeat( @_, try => 1 );
440             }
441              
442             =head2 try_repeat_until_success
443              
444             $future = try_repeat_until_success { CODE } ...;
445              
446             I
447              
448             A shortcut to calling C with an ending condition that simply tests
449             for a successful result from a future. May be combined with C or
450             C.
451              
452             This function used to be called C, and is currently
453             aliased as this name as well.
454              
455             =cut
456              
457             sub try_repeat_until_success(&@)
458             {
459 2     2 1 2043 my $code = shift;
460 2         10 my %args = @_;
461              
462             # TODO: maybe merge while/until conditions one day...
463             defined($args{while}) or defined($args{until})
464 2 50 33     14 and croak "Cannot pass 'while' or 'until' to try_repeat_until_success";
465              
466             # defeat prototype
467 2     5   13 &try_repeat( $code, while => sub { shift->failure }, %args );
  5         17  
468             }
469              
470             # Legacy name
471             *repeat_until_success = \&try_repeat_until_success;
472              
473             =head1 APPLYING A FUNCTION TO A LIST
474              
475             The C family of functions provide a way to call a block of code that
476             returns a L (called here an "item future") once per item in a given
477             list, or returned by a generator function. The C functions themselves
478             return a C to represent the ongoing operation, which completes when
479             every item's future has completed.
480              
481             While this behaviour can also be implemented using C, the main reason
482             to use an C function is that the individual item operations are
483             considered as independent, and thus more than one can be outstanding
484             concurrently. An argument can be passed to the function to indicate how many
485             items to start initially, and thereafter it will keep that many of them
486             running concurrently until all of the items are done, or until any of them
487             fail. If an individual item future fails, the overall result future will be
488             marked as failing with the same failure, and any other pending item futures
489             that are outstanding at the time will be cancelled.
490              
491             The following named arguments are common to each C function:
492              
493             =over 8
494              
495             =item foreach => ARRAY
496              
497             Provides the list of items to iterate over, as an C reference.
498              
499             The referenced array will be modified by this operation, Cing one item
500             from it each time. The can C more items to this array as it runs, and
501             they will be included in the iteration.
502              
503             =item generate => CODE
504              
505             Provides the list of items to iterate over, by calling the generator function
506             once for each required item. The function should return a single item, or an
507             empty list to indicate it has no more items.
508              
509             ( $item ) = $generate->();
510              
511             This function will be invoked each time any previous item future has completed
512             and may be called again even after it has returned empty.
513              
514             =item concurrent => INT
515              
516             Gives the number of item futures to keep outstanding. By default this value
517             will be 1 (i.e. no concurrency); larger values indicate that multiple item
518             futures will be started at once.
519              
520             =item return => Future
521              
522             Normally, a new instance is returned by cloning the first non-immediate future
523             returned as an item future. By passing a new instance as the C
524             argument, the result will be put into the given instance. This can be used to
525             return subclasses, or specific instances.
526              
527             =back
528              
529             In each case, the main code block will be called once for each item in the
530             list, passing in the item as the only argument:
531              
532             $item_f = $code->( $item );
533              
534             The expected return value from each item's future, and the value returned from
535             the result future will differ in each function's case; they are documented
536             below.
537              
538             For similarity with perl's core C function, the item is also available
539             aliased as C<$_>.
540              
541             =cut
542              
543             # This function is invoked in two circumstances:
544             # a) to create an item Future in a slot,
545             # b) once a non-immediate item Future is complete, to check its results
546             # It can tell which circumstance by whether the slot itself is defined or not
547             sub _fmap_slot
548             {
549 60     60   146 my ( $slots, undef, $code, $generator, $collect, $results, $return ) = @_;
550              
551 60         72 SLOT: while(1) {
552             # Capture args each call because we mutate them
553 99         219 my ( undef, $idx ) = my @args = @_;
554              
555 99 100       191 unless( $slots->[$idx] ) {
556             # No item Future yet (case a), so create one
557 69         159 my $item;
558 69 100       124 unless( ( $item ) = $generator->() ) {
559             # All out of items, so now just wait for the slots to be finished
560 26         42 undef $slots->[$idx];
561 26   100     104 defined and return $return for @$slots;
562              
563             # All the slots are done
564 11   66     26 $return ||= Future->new;
565              
566 11         34 $return->done( @$results );
567 11         73 return $return;
568             }
569              
570 43         202 my $f = $slots->[$idx] = Future->call( $code, local $_ = $item );
571              
572 43 100       113 if( $collect eq "array" ) {
    100          
573 10         42 push @$results, my $r = [];
574 10     9   68 $f->on_done( sub { @$r = @_ });
  9         31  
575             }
576             elsif( $collect eq "scalar" ) {
577 3         7 push @$results, undef;
578 3         6 my $r = \$results->[-1];
579 3     3   16 $f->on_done( sub { $$r = $_[0] });
  3         10  
580             }
581             }
582              
583 73         130 my $f = $slots->[$idx];
584              
585             # Slot is non-immediate; arrange for us to be invoked again later when it's ready
586 73 100       149 if( !$f->is_ready ) {
587 36   66     95 $args[-1] = ( $return ||= $f->new );
588 36     30   174 $f->on_done( sub { _fmap_slot( @args ) } );
  30         58  
589 36         121 $f->on_fail( $return );
590              
591             # Try looking for more that might be ready
592 36         49 my $i = $idx + 1;
593 36         74 while( $i != $idx ) {
594 40         46 $i++;
595 40         63 $i %= @$slots;
596 40 100       179 next if defined $slots->[$i];
597              
598 3         5 $_[1] = $i;
599 3         4 redo SLOT;
600             }
601 33         112 return $return;
602             }
603              
604             # Either we've been invoked again (case b), or the immediate Future was
605             # already ready.
606 37 100       78 if( $f->failure ) {
607 1   33     3 $return ||= $f->new;
608 1         2 $return->fail( $f->failure );
609 1         2 return $return;
610             }
611              
612 36         89 undef $slots->[$idx];
613             # next
614             }
615             }
616              
617             sub _fmap
618             {
619 15     15   26 my $code = shift;
620 15         46 my %args = @_;
621              
622 15   100     58 my $concurrent = $args{concurrent} || 1;
623 15         24 my @slots;
624              
625 15         21 my $results = [];
626 15         25 my $future = $args{return};
627              
628 15         18 my $generator;
629 15 100       51 if( $generator = $args{generate} ) {
    50          
630             # OK
631             }
632             elsif( my $array = $args{foreach} ) {
633 14 100   65   43 $generator = sub { return unless @$array; shift @$array };
  65         142  
  40         101  
634             }
635             else {
636 0         0 croak "Expected either 'generate' or 'foreach'";
637             }
638              
639             # If any of these immediately fail, don't bother continuing
640 15         48 foreach my $idx ( 0 .. $concurrent-1 ) {
641 30         75 $future = _fmap_slot( \@slots, $idx, $code, $generator, $args{collect}, $results, $future );
642 30 100       72 last if $future->is_ready;
643             }
644              
645             $future->on_fail( sub {
646 2   66 2   9 !defined $_ or $_->is_ready or $_->cancel for @slots;
      66        
647 15         78 });
648             $future->on_cancel( sub {
649 2   66 2   28 !defined $_ or $_->is_ready or $_->cancel for @slots;
      66        
650 15         85 });
651              
652 15         95 return $future;
653             }
654              
655             =head2 fmap_concat
656              
657             $future = fmap_concat { CODE } ...;
658              
659             I
660              
661             This version of C expects each item future to return a list of zero or
662             more values, and the overall result will be the concatenation of all these
663             results. It acts like a future-based equivalent to Perl's C operator.
664              
665             The results are returned in the order of the original input values, not in the
666             order their futures complete in. Because of the intermediate storage of
667             C references and final flattening operation used to implement this
668             behaviour, this function is slightly less efficient than C or
669             C in cases where item futures are expected only ever to return one,
670             or zero values, respectively.
671              
672             This function is also available under the name of simply C to emphasise
673             its similarity to perl's C keyword.
674              
675             =cut
676              
677             sub fmap_concat(&@)
678             {
679 4     4 1 227578 my $code = shift;
680 4         15 my %args = @_;
681              
682             _fmap( $code, %args, collect => "array" )->then( sub {
683 3     3   11 return Future->done( map { @$_ } @_ );
  9         28  
684 4         20 });
685             }
686             *fmap = \&fmap_concat;
687              
688             =head2 fmap_scalar
689              
690             $future = fmap_scalar { CODE } ...;
691              
692             I
693              
694             This version of C acts more like the C functions found in Scheme or
695             Haskell; it expects that each item future returns only one value, and the
696             overall result will be a list containing these, in order of the original input
697             items. If an item future returns more than one value the others will be
698             discarded. If it returns no value, then C will be substituted in its
699             place so that the result list remains in correspondence with the input list.
700              
701             This function is also available under the shorter name of C.
702              
703             =cut
704              
705             sub fmap_scalar(&@)
706             {
707 1     1 1 308 my $code = shift;
708 1         4 my %args = @_;
709              
710 1         5 _fmap( $code, %args, collect => "scalar" )
711             }
712             *fmap1 = \&fmap_scalar;
713              
714             =head2 fmap_void
715              
716             $future = fmap_void { CODE } ...;
717              
718             I
719              
720             This version of C does not collect any results from its item futures, it
721             simply waits for them all to complete. Its result future will provide no
722             values.
723              
724             While not a map in the strictest sense, this variant is still useful as a way
725             to control concurrency of a function call iterating over a list of items,
726             obtaining its results by some other means (such as side-effects on captured
727             variables, or some external system).
728              
729             This function is also available under the shorter name of C.
730              
731             =cut
732              
733             sub fmap_void(&@)
734             {
735 10     10 1 254680 my $code = shift;
736 10         28 my %args = @_;
737              
738 10         52 _fmap( $code, %args, collect => "void" )
739             }
740             *fmap0 = \&fmap_void;
741              
742             =head1 AUTHOR
743              
744             Paul Evans
745              
746             =cut
747              
748             0x55AA;