diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2015-03-10 19:55:44 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2015-03-10 19:55:44 +0000 |
commit | 8cc5160aefb2ba3611d1d5d6b12b996227f9da72 (patch) | |
tree | 256923c9d568f659ca72d254993e6a40c439a7b5 /lib/Future | |
download | Future-tarball-master.tar.gz |
Future-0.32HEADFuture-0.32master
Diffstat (limited to 'lib/Future')
-rw-r--r-- | lib/Future/Phrasebook.pod | 500 | ||||
-rw-r--r-- | lib/Future/Utils.pm | 687 |
2 files changed, 1187 insertions, 0 deletions
diff --git a/lib/Future/Phrasebook.pod b/lib/Future/Phrasebook.pod new file mode 100644 index 0000000..2798536 --- /dev/null +++ b/lib/Future/Phrasebook.pod @@ -0,0 +1,500 @@ +# You may distribute under the terms of either the GNU General Public License +# or the Artistic License (the same terms as Perl itself) +# +# (C) Paul Evans, 2013-2014 -- leonerd@leonerd.org.uk + +=head1 NAME + +C<Future::Phrasebook> - coding examples for C<Future> and C<Future::Utils> + +This documentation-only module provides a phrasebook-like approach to giving +examples on how to use L<Future> and L<Future::Utils> to structure +Future-driven asynchronous or concurrent logic. As with any inter-dialect +phrasebook it is structured into pairs of examples; each given first in a +traditional call/return Perl style, and second in a style using Futures. In +each case, the generic function or functions in the example are named in +C<ALL_CAPITALS()> to make them stand out. + +In the examples showing use of Futures, any function that is expected to +return a C<Future> instance is named with a leading C<F_> prefix. Each example +is also constructed so as to yield an overall future in a variable called +C<$f>, which represents the entire operation. + +=head1 SEQUENCING + +The simplest example of a sequencing operation is simply running one piece of +code, then immediately running a second. In call/return code we can just place +one after the other. + + FIRST(); + SECOND(); + +Using a Future it is necessary to await the result of the first C<Future> +before calling the second. + + my $f = F_FIRST() + ->then( sub { F_SECOND(); } ); + +Here, the anonymous closure is invoked once the C<Future> returned by +C<F_FIRST()> succeeds. Because C<then> invokes the code block only if the +first Future succeeds, it shortcircuits around failures similar to the way that +C<die()> shortcircuits around thrown exceptions. A C<Future> representing the +entire combination is returned by the method. + +Because the C<then> method itself returns a C<Future> representing the +overall operation, it can itself be further chained. + + FIRST(); + SECOND(); + THIRD(); + +Z<> + + my $f = F_FIRST() + ->then( sub { F_SECOND(); } ) + ->then( sub { F_THIRD(); } ); + +See below for examples of ways to handle exceptions. + +=head2 Passing Results + +Often the result of one function can be passed as an argument to another +function. + + OUTER( INNER() ); + +The result of the first C<Future> is passed into the code block given to the +C<then> method. + + my $f = F_INNER() + ->then( sub { F_OUTER( @_ ) } ); + +=head1 CONDITIONALS + +It may be that the result of one function call is used to determine whether or +not another operation is taken. + + if( COND() == $value ) { + ACTION(); + } + +Because the C<then_with_f> code block is given the first future in addition to +its results it can decide whether to call the second function to return a new +future, or simply return the one it was given. + + my $f = F_COND() + ->then_with_f( sub { + my ( $f_cond, $result ) = @_; + if( $result == $value ) { + return F_ACTION(); + } + else { + return $f_cond; + } + }); + +=head1 EXCEPTION HANDLING + +In regular call/return style code, if any function throws an exception, the +remainder of the block is not executed, the containing C<try> or C<eval> is +aborted, and control is passed to the corresponding C<catch> or line after the +C<eval>. + + try { + FIRST(); + } + catch { + my $e = $_; + ERROR( $e ); + }; + +The C<else> method on a C<Future> can be used here. It behaves similar to +C<then>, but is only invoked if the initial C<Future> fails; not if it +succeeds. + + my $f = F_FIRST() + ->else( sub { F_ERROR( @_ ); } ); + +Alternatively, the second argument to the C<then> method can be applied, which +is invoked only on case of failure. + + my $f = F_FIRST() + ->then( undef, sub { F_ERROR( @_ ); } ); + +Often it may be the case that the failure-handling code is in fact immediate, +and doesn't return a C<Future>. In that case, the C<else> code block can +return an immediate C<Future> instance. + + my $f = F_FIRST() + ->else( sub { + ERROR( @_ ); + return Future->done; + }); + +Sometimes the failure handling code simply needs to be aware of the failure, +but rethrow it further up. + + try { + FIRST(); + } + catch { + my $e = $_; + ERROR( $e ); + die $e; + }; + +In this case, while the C<else> block could return a new C<Future> failed with +the same exception, the C<else_with_f> block is passed the failed C<Future> +itself in addition to the failure details so it can just return that. + + my $f = F_FIRST() + ->else_with_f( sub { + my ( $f1, @failure ) = @_; + ERROR( @failure ); + return $f1; + }); + +The C<followed_by> method is similar again, though it invokes the code block +regardless of the success or failure of the initial C<Future>. It can be used +to create C<finally> semantics. By returning the C<Future> instance that it +was passed, the C<followed_by> code ensures it doesn't affect the result of +the operation. + + try { + FIRST(); + } + catch { + ERROR( $_ ); + } + finally { + CLEANUP(); + }; + +Z<> + + my $f = F_FIRST() + ->else( sub { + ERROR( @_ ); + return Future->done; + }) + ->followed_by( sub { + CLEANUP(); + return shift; + }); + +=head1 ITERATION + +To repeat a single block of code multiple times, a C<while> block is often +used. + + while( COND() ) { + FUNC(); + } + +The C<Future::Utils::repeat> function can be used to repeatedly iterate a +given C<Future>-returning block of code until its ending condition is +satisfied. + + use Future::Utils qw( repeat ); + my $f = repeat { + F_FUNC(); + } while => sub { COND() }; + +Unlike the statement nature of perl's C<while> block, this C<repeat> C<Future> +can yield a value; the value returned by C<< $f->get >> is the result of the +final trial of the code block. + +Here, the condition function it expected to return its result immediately. If +the repeat condition function itself returns a C<Future>, it can be combined +along with the loop body. The trial C<Future> returned by the code block is +passed to the C<while> condition function. + + my $f = repeat { + F_FUNC() + ->followed_by( sub { F_COND(); } ); + } while => sub { shift->get }; + +The condition can be negated by using C<until> instead + + until( HALTING_COND() ) { + FUNC(); + } + +Z<> + + my $f = repeat { + F_FUNC(); + } until => sub { HALTING_COND() }; + +=head2 Iterating with Exceptions + +Technically, this loop isn't quite the same as the equivalent C<while> loop in +plain Perl, because the C<while> loop will also stop executing if the code +within it throws an exception. This can be handled in C<repeat> by testing for +a failed C<Future> in the C<until> condition. + + while(1) { + TRIAL(); + } + +Z<> + + my $f = repeat { + F_TRIAL(); + } until => sub { shift->failure }; + +When a repeat loop is required to retry a failure, the C<try_repeat> function +should be used. Currently this function behaves equivalently to C<repeat>, +except that it will not print a warning if it is asked to retry after a +failure, whereas this behaviour is now deprecated for the regular C<repeat> +function so that yields a warning. + + my $f = try_repeat { + F_TRIAL(); + } while => sub { shift->failure }; + +Another variation is the C<try_repeat_until_success> function, which provides +a convenient shortcut to calling C<try_repeat> with a condition that makes +another attempt each time the previous one fails; stopping once it achieves a +successful result. + + while(1) { + eval { TRIAL(); 1 } and last; + } + +Z<> + + my $f = try_repeat_until_success { + F_TRIAL(); + }; + +=head2 Iterating over a List + +A variation on the idea of the C<while> loop is the C<foreach> loop; a loop +that executes once for each item in a given list, with a variable set to one +value from that list each time. + + foreach my $thing ( @THINGS ) { + INSPECT( $thing ); + } + +This can be performed with C<Future> using the C<foreach> parameter to the +C<repeat> function. When this is in effect, the block of code is passed each +item of the given list as the first parameter. + + my $f = repeat { + my $thing = shift; + F_INSPECT( $thing ); + } foreach => \@THINGS; + +=head2 Recursing over a Tree + +A regular call/return function can use recursion to walk over a tree-shaped +structure, where each item yields a list of child items. + + sub WALK + { + my ( $item ) = @_; + ... + WALK($_) foreach CHILDREN($item); + } + +This recursive structure can be turned into a C<while()>-based repeat loop by +using an array to store the remaining items to walk into, instead of using the +perl stack directly: + + sub WALK + { + my @more = ( $root ); + while( @more ) { + my $item = shift @more; + ... + unshift @more, CHILDREN($item) + } + } + +This arrangement then allows us to use C<fmap_void> to walk this structure +using Futures, possibly concurrently. A lexical array variable is captured +that holds the stack of remaining items, which is captured by the item code so +it can C<unshift> more into it, while also being used as the actual C<fmap> +control array. + + my @more = ( $root ); + + my $f = fmap_void { + my $item = shift; + ...->on_done( sub { + unshift @more, @CHILDREN; + }) + } foreach => \@more; + +By choosing to either C<unshift> or C<push> more items onto this list, the +tree can be walked in either depth-first or breadth-first order. + +=head1 SHORT-CIRCUITING + +Sometimes a result is determined that should be returned through several +levels of control structure. Regular Perl code has such keywords as C<return> +to return a value from a function immediately, or C<last> for immediately +stopping execution of a loop. + + sub func { + foreach my $item ( @LIST ) { + if( COND($item) ) { + return $item; + } + } + return MAKE_NEW_ITEM(); + } + +The C<Future::Utils::call_with_escape> function allows this general form of +control flow, by calling a block of code that is expected to return a future, +and itself returning a future. Under normal circumstances the result of this +future propagates through to the one returned by C<call_with_escape>. + +However, the code is also passed in a future value, called here the "escape +future". If the code captures this future and completes it (either by calling +C<done> or C<fail>), then the overall returned future immediately completes +with that result instead, and the future returned by the code block is +cancelled. + + my $f = call_with_escape { + my $escape_f = shift; + + ( repeat { + my $item = shift; + COND($item)->then( sub { + my ( $result ) = @_; + if( $result ) { + $escape_f->done( $item ); + } + return Future->done; + }) + } foreach => \@ITEMS )->then( sub { + MAKE_NEW_ITEM(); + }); + }; + +Here, if C<$escape_f> is completed by the condition test, the future chain +returned by the code (that is, the C<then> chain of the C<repeat> block +followed by C<MAKE_NEW_ITEM()>) will be cancelled, and C<$f> itself will +receive this result. + +=head1 CONCURRENCY + +This final section of the phrasebook demonstrates a number of abilities that +are simple to do with C<Future> but can't easily be done with regular +call/return style programming, because they all involve an element of +concurrency. In these examples the comparison with regular call/return code +will be somewhat less accurate because of the inherent ability for the +C<Future>-using version to behave concurrently. + +=head2 Waiting on Multiple Functions + +The C<< Future->wait_all >> constructor creates a C<Future> that waits for all +of the component futures to complete. This can be used to form a sequence with +concurrency. + + { FIRST_A(); FIRST_B() } + SECOND(); + +Z<> + + my $f = Future->wait_all( FIRST_A(), FIRST_B() ) + ->then( sub { SECOND() } ); + +Unlike in the call/return case, this can perform the work of C<FIRST_A()> and +C<FIRST_B()> concurrently, only proceeding to C<SECOND()> when both are ready. + +The result of the C<wait_all> C<Future> is the list of its component +C<Future>s. This can be used to obtain the results. + + SECOND( FIRST_A(), FIRST_B() ); + +Z<> + + my $f = Future->wait_all( FIRST_A(), FIRST_B() ) + ->then( sub { + my ( $f_a, $f_b ) = @_ + SECOND( $f_a->get, $f_b->get ); + } ); + +Because the C<get> method will re-raise an exception caused by a failure of +either of the C<FIRST> functions, the second stage will fail if any of the +initial Futures failed. + +As this is likely to be the desired behaviour most of the time, this kind of +control flow can be written slightly neater using C<< Future->needs_all >> +instead. + + my $f = Future->needs_all( FIRST_A(), FIRST_B() ) + ->then( sub { SECOND( @_ ) } ); + +The C<get> method of a C<needs_all> convergent Future returns a concatenated +list of the results of all its component Futures, as the only way it will +succeed is if all the components do. + +=head2 Waiting on Multiple Calls of One Function + +Because the C<wait_all> and C<needs_all> constructors take an entire list of +C<Future> instances, they can be conveniently used with C<map> to wait on the +result of calling a function concurrently once per item in a list. + + my @RESULT = map { FUNC( $_ ) } @ITEMS; + PROCESS( @RESULT ); + +Again, the C<needs_all> version allows more convenient access to the list of +results. + + my $f = Future->needs_all( map { F_FUNC( $_ ) } @ITEMS ) + ->then( sub { + my @RESULT = @_; + F_PROCESS( @RESULT ) + } ); + +This form of the code starts every item's future concurrently, then waits for +all of them. If the list of C<@ITEMS> is potentially large, this may cause a +problem due to too many items running at once. Instead, the +C<Future::Utils::fmap> family of functions can be used to bound the +concurrency, keeping at most some given number of items running, starting new +ones as existing ones complete. + + my $f = fmap { + my $item = shift; + F_FUNC( $item ) + } foreach => \@ITEMS; + +By itself, this will not actually act concurrently as it will only keep one +Future outstanding at a time. The C<concurrent> flag lets it keep a larger +number "in flight" at any one time: + + my $f = fmap { + my $item = shift; + F_FUNC( $item ) + } foreach => \@ITEMS, concurrent => 10; + +The C<fmap> and C<fmap_scalar> functions return a Future that will eventually +give the collected results of the individual item futures, thus making them +similar to perl's C<map> operator. + +Sometimes, no result is required, and the items are run in a loop simply for +some side-effect of the body. + + foreach my $item ( @ITEMS ) { + FUNC( $item ); + } + +To avoid having to collect a potentially-large set of results only to throw +them away, the C<fmap_void> function variant of the C<fmap> family yields a +Future that completes with no result after all the items are complete. + + my $f = fmap_void { + my $item = shift; + F_FIRST( $item ) + } foreach => \@ITEMS, concurrent => 10; + +=head1 AUTHOR + +Paul Evans <leonerd@leonerd.org.uk> + +=cut diff --git a/lib/Future/Utils.pm b/lib/Future/Utils.pm new file mode 100644 index 0000000..563f327 --- /dev/null +++ b/lib/Future/Utils.pm @@ -0,0 +1,687 @@ +# You may distribute under the terms of either the GNU General Public License +# or the Artistic License (the same terms as Perl itself) +# +# (C) Paul Evans, 2013-2015 -- leonerd@leonerd.org.uk + +package Future::Utils; + +use strict; +use warnings; + +our $VERSION = '0.32'; + +use Exporter 'import'; +# Can't import the one from Exporter as it relies on package inheritance +sub export_to_level +{ + my $pkg = shift; local $Exporter::ExportLevel = 1 + shift; $pkg->import(@_); +} + +our @EXPORT_OK = qw( + call + call_with_escape + + repeat + try_repeat try_repeat_until_success + repeat_until_success + + fmap fmap_concat + fmap1 fmap_scalar + fmap0 fmap_void +); + +use Carp; +our @CARP_NOT = qw( Future ); + +use Future; + +=head1 NAME + +C<Future::Utils> - utility functions for working with C<Future> objects + +=head1 SYNOPSIS + + use Future::Utils qw( call_with_escape ); + + my $result_f = call_with_escape { + my $escape_f = shift; + my $f = ... + $escape_f->done( "immediate result" ); + ... + }; + +Z<> + + use Future::Utils qw( repeat try_repeat try_repeat_until_success ); + + my $eventual_f = repeat { + my $trial_f = ... + return $trial_f; + } while => sub { my $f = shift; return want_more($f) }; + + my $eventual_f = repeat { + ... + return $trail_f; + } until => sub { my $f = shift; return acceptable($f) }; + + my $eventual_f = repeat { + my $item = shift; + ... + return $trial_f; + } foreach => \@items; + + my $eventual_f = try_repeat { + my $trial_f = ... + return $trial_f; + } while => sub { ... }; + + my $eventual_f = try_repeat_until_success { + ... + return $trial_f; + }; + + my $eventual_f = try_repeat_until_success { + my $item = shift; + ... + return $trial_f; + } foreach => \@items; + +Z<> + + use Future::Utils qw( fmap_concat fmap_scalar fmap_void ); + + my $result_f = fmap_concat { + my $item = shift; + ... + return $item_f; + } foreach => \@items, concurrent => 4; + + my $result_f = fmap_scalar { + my $item = shift; + ... + return $item_f; + } foreach => \@items, concurrent => 8; + + my $done_f = fmap_void { + my $item = shift; + ... + return $item_f; + } foreach => \@items, concurrent => 10; + +=cut + +=head1 INVOKING A BLOCK OF CODE + +=head2 $f = call { CODE } + +The C<call> function invokes a block of code that returns a future, and simply +returns the future it returned. The code is wrapped in an C<eval {}> block, so +that if it throws an exception this is turned into an immediate failed +C<Future>. If the code does not return a C<Future>, then an immediate failed +C<Future> instead. + +(This is equivalent to using C<< Future->call >>, but is duplicated here for +completeness). + +=cut + +sub call(&) +{ + my ( $code ) = @_; + return Future->call( $code ); +} + +=head2 $f = call_with_escape { CODE } + +The C<call_with_escape> function invokes a block of code that returns a +future, and passes in a separate future (called here an "escape future"). +Normally this is equivalent to the simple C<call> function. However, if the +code captures this future and completes it by calling C<done> or C<fail> on +it, the future returned by C<call_with_escape> immediately completes with this +result, and the future returned by the code itself is cancelled. + +This can be used to implement short-circuit return from an iterating loop or +complex sequence of code, or immediate fail that bypasses failure handling +logic in the code itself, or several other code patterns. + + $f = $code->( $escape_f ) + +(This can be considered similar to C<call-with-escape-continuation> as found +in some Scheme implementations). + +=cut + +sub call_with_escape(&) +{ + my ( $code ) = @_; + + my $escape_f = Future->new; + + return Future->wait_any( + Future->call( $code, $escape_f ), + $escape_f, + ); +} + +=head1 REPEATING A BLOCK OF CODE + +The C<repeat> function provides a way to repeatedly call a block of code that +returns a L<Future> (called here a "trial future") until some ending condition +is satisfied. The C<repeat> function itself returns a C<Future> to represent +running the repeating loop until that end condition (called here the "eventual +future"). The first time the code block is called, it is passed no arguments, +and each subsequent invocation is passed the previous trial future. + +The result of the eventual future is the result of the last trial future. + +If the eventual future is cancelled, the latest trial future will be +cancelled. + +If some specific subclass or instance of C<Future> is required as the return +value, it can be passed as the C<return> argument. Otherwise the return value +will be constructed by cloning the first non-immediate trial C<Future>. + +=head2 $future = repeat { CODE } while => CODE + +Repeatedly calls the C<CODE> block while the C<while> condition returns a true +value. Each time the trial future completes, the C<while> condition is passed +the trial future. + + $trial_f = $code->( $previous_trial_f ) + $again = $while->( $trial_f ) + +If the C<$code> block dies entirely and throws an exception, this will be +caught and considered as an immediately-failed C<Future> with the exception as +the future's failure. The exception will not be propagated to the caller. + +=head2 $future = repeat { CODE } until => CODE + +Repeatedly calls the C<CODE> block until the C<until> condition returns a true +value. Each time the trial future completes, the C<until> condition is passed +the trial future. + + $trial_f = $code->( $previous_trial_f ) + $accept = $until->( $trial_f ) + +=head2 $future = repeat { CODE } foreach => ARRAY, otherwise => CODE + +Calls the C<CODE> block once for each value obtained from the array, passing +in the value as the first argument (before the previous trial future). When +there are no more items left in the array, the C<otherwise> code is invoked +once and passed the last trial future, if there was one, or C<undef> if the +list was originally empty. The result of the eventual future will be the +result of the future returned from C<otherwise>. + +The referenced array may be modified by this operation. + + $trial_f = $code->( $item, $previous_trial_f ) + $final_f = $otherwise->( $last_trial_f ) + +The C<otherwise> code is optional; if not supplied then the result of the +eventual future will simply be that of the last trial. If there was no trial, +because the C<foreach> list was already empty, then an immediate successful +future with an empty result is returned. + +=head2 $future = repeat { CODE } foreach => ARRAY, while => CODE, ... + +=head2 $future = repeat { CODE } foreach => ARRAY, until => CODE, ... + +Combines the effects of C<foreach> with C<while> or C<until>. Calls the +C<CODE> block once for each value obtained from the array, until the array is +exhausted or the given ending condition is satisfied. + +If a C<while> or C<until> condition is combined with C<otherwise>, the +C<otherwise> code will only be run if the array was entirely exhausted. If the +operation is terminated early due to the C<while> or C<until> condition being +satisfied, the eventual result will simply be that of the last trial that was +executed. + +=head2 $future = repeat { CODE } generate => CODE, otherwise => CODE + +Calls the C<CODE> block once for each value obtained from the generator code, +passing in the value as the first argument (before the previous trial future). +When the generator returns an empty list, the C<otherwise> code is invoked and +passed the last trial future, if there was one, otherwise C<undef> if the +generator never returned a value. The result of the eventual future will be +the result of the future returned from C<otherwise>. + + $trial_f = $code->( $item, $previous_trial_f ) + $final_f = $otherwise->( $last_trial_f ) + + ( $item ) = $generate->() + +The generator is called in list context but should return only one item per +call. Subsequent values will be ignored. When it has no more items to return +it should return an empty list. + +For backward compatibility this function will allow a C<while> or C<until> +condition that requests a failure be repeated, but it will print a warning if +it has to do that. To apply repeating behaviour that can catch and retry +failures, use C<try_repeat> instead. This old behaviour is now deprecated and +will be removed in the next version. + +=cut + +sub _repeat +{ + my ( $code, $return, $trialp, $cond, $sense, $is_try ) = @_; + + my $prev = $$trialp; + + while(1) { + my $trial = $$trialp ||= Future->call( $code, $prev ); + $prev = $trial; + + if( !$trial->is_ready ) { + # defer + $return ||= $trial->new; + $trial->on_ready( sub { + return if $$trialp->is_cancelled; + _repeat( $code, $return, $trialp, $cond, $sense, $is_try ); + }); + return $return; + } + + my $stop; + if( not eval { $stop = !$cond->( $trial ) ^ $sense; 1 } ) { + $return ||= $trial->new; + $return->fail( $@ ); + return $return; + } + + if( $stop ) { + # Return result + $return ||= $trial->new; + $trial->on_done( $return ); + $trial->on_fail( $return ); + return $return; + } + + if( !$is_try and $trial->failure ) { + carp "Using Future::Utils::repeat to retry a failure is deprecated; use try_repeat instead"; + } + + # redo + undef $$trialp; + } +} + +sub repeat(&@) +{ + my $code = shift; + my %args = @_; + + # This makes it easier to account for other conditions + defined($args{while}) + defined($args{until}) == 1 + or defined($args{foreach}) + or defined($args{generate}) + or croak "Expected one of 'while', 'until', 'foreach' or 'generate'"; + + if( $args{foreach} ) { + $args{generate} and croak "Cannot use both 'foreach' and 'generate'"; + + my $array = delete $args{foreach}; + $args{generate} = sub { + @$array ? shift @$array : (); + }; + } + + if( $args{generate} ) { + my $generator = delete $args{generate}; + my $otherwise = delete $args{otherwise}; + + # TODO: This is slightly messy as this lexical is captured by both + # blocks of code. Can we do better somehow? + my $done; + + my $orig_code = $code; + $code = sub { + my ( $last_trial_f ) = @_; + my $again = my ( $value ) = $generator->( $last_trial_f ); + + if( $again ) { + unshift @_, $value; goto &$orig_code; + } + + $done++; + if( $otherwise ) { + goto &$otherwise; + } + else { + return $last_trial_f || Future->done; + } + }; + + if( my $orig_while = delete $args{while} ) { + $args{while} = sub { + $orig_while->( $_[0] ) and !$done; + }; + } + elsif( my $orig_until = delete $args{until} ) { + $args{while} = sub { + !$orig_until->( $_[0] ) and !$done; + }; + } + else { + $args{while} = sub { !$done }; + } + } + + my $future = $args{return}; + + my $trial; + $args{while} and $future = _repeat( $code, $future, \$trial, $args{while}, 0, $args{try} ); + $args{until} and $future = _repeat( $code, $future, \$trial, $args{until}, 1, $args{try} ); + + $future->on_cancel( sub { $trial->cancel } ); + + return $future; +} + +=head2 $future = try_repeat { CODE } ... + +A variant of C<repeat> that doesn't warn when the trial fails and the +condition code asks for it to be repeated. + +In some later version the C<repeat> function will be changed so that if a +trial future fails, then the eventual future will immediately fail as well, +making its semantics a little closer to that of a C<while {}> loop in Perl. +Code that specifically wishes to catch failures in trial futures and retry +the block should use C<try_repeat> specifically. + +=cut + +sub try_repeat(&@) +{ + # defeat prototype + &repeat( @_, try => 1 ); +} + +=head2 $future = try_repeat_until_success { CODE } ... + +A shortcut to calling C<try_repeat> with an ending condition that simply tests +for a successful result from a future. May be combined with C<foreach> or +C<generate>. + +This function used to be called C<repeat_until_success>, and is currently +aliased as this name as well. + +=cut + +sub try_repeat_until_success(&@) +{ + my $code = shift; + my %args = @_; + + # TODO: maybe merge while/until conditions one day... + defined($args{while}) or defined($args{until}) + and croak "Cannot pass 'while' or 'until' to try_repeat_until_success"; + + # defeat prototype + &try_repeat( $code, while => sub { shift->failure }, %args ); +} + +# Legacy name +*repeat_until_success = \&try_repeat_until_success; + +=head1 APPLYING A FUNCTION TO A LIST + +The C<fmap> family of functions provide a way to call a block of code that +returns a L<Future> (called here an "item future") once per item in a given +list, or returned by a generator function. The C<fmap*> functions themselves +return a C<Future> to represent the ongoing operation, which completes when +every item's future has completed. + +While this behaviour can also be implemented using C<repeat>, the main reason +to use an C<fmap> function is that the individual item operations are +considered as independent, and thus more than one can be outstanding +concurrently. An argument can be passed to the function to indicate how many +items to start initially, and thereafter it will keep that many of them +running concurrently until all of the items are done, or until any of them +fail. If an individual item future fails, the overall result future will be +marked as failing with the same failure, and any other pending item futures +that are outstanding at the time will be cancelled. + +The following named arguments are common to each C<fmap*> function: + +=over 8 + +=item foreach => ARRAY + +Provides the list of items to iterate over, as an C<ARRAY> reference. + +The referenced array will be modified by this operation, C<shift>ing one item +from it each time. The can C<push> more items to this array as it runs, and +they will be included in the iteration. + +=item generate => CODE + +Provides the list of items to iterate over, by calling the generator function +once for each required item. The function should return a single item, or an +empty list to indicate it has no more items. + + ( $item ) = $generate->() + +This function will be invoked each time any previous item future has completed +and may be called again even after it has returned empty. + +=item concurrent => INT + +Gives the number of item futures to keep outstanding. By default this value +will be 1 (i.e. no concurrency); larger values indicate that multiple item +futures will be started at once. + +=item return => Future + +Normally, a new instance is returned by cloning the first non-immediate future +returned as an item future. By passing a new instance as the C<return> +argument, the result will be put into the given instance. This can be used to +return subclasses, or specific instances. + +=back + +In each case, the main code block will be called once for each item in the +list, passing in the item as the only argument: + + $item_f = $code->( $item ) + +The expected return value from each item's future, and the value returned from +the result future will differ in each function's case; they are documented +below. + +=cut + +# This function is invoked in two circumstances: +# a) to create an item Future in a slot, +# b) once a non-immediate item Future is complete, to check its results +# It can tell which circumstance by whether the slot itself is defined or not +sub _fmap_slot +{ + my ( $slots, undef, $code, $generator, $collect, $results, $return ) = @_; + + SLOT: while(1) { + # Capture args each call because we mutate them + my ( undef, $idx ) = my @args = @_; + + unless( $slots->[$idx] ) { + # No item Future yet (case a), so create one + my $item; + unless( ( $item ) = $generator->() ) { + # All out of items, so now just wait for the slots to be finished + undef $slots->[$idx]; + defined and return $return for @$slots; + + # All the slots are done + $return ||= Future->new; + + $return->done( @$results ); + return $return; + } + + my $f = $slots->[$idx] = Future->call( $code, $item ); + + if( $collect eq "array" ) { + push @$results, my $r = []; + $f->on_done( sub { @$r = @_ }); + } + elsif( $collect eq "scalar" ) { + push @$results, undef; + my $r = \$results->[-1]; + $f->on_done( sub { $$r = $_[0] }); + } + } + + my $f = $slots->[$idx]; + + # Slot is non-immediate; arrange for us to be invoked again later when it's ready + if( !$f->is_ready ) { + $args[-1] = ( $return ||= $f->new ); + $f->on_done( sub { _fmap_slot( @args ) } ); + $f->on_fail( $return ); + + # Try looking for more that might be ready + my $i = $idx + 1; + while( $i != $idx ) { + $i++; + $i %= @$slots; + next if defined $slots->[$i]; + + $_[1] = $i; + redo SLOT; + } + return $return; + } + + # Either we've been invoked again (case b), or the immediate Future was + # already ready. + if( $f->failure ) { + $return ||= $f->new; + $return->fail( $f->failure ); + return $return; + } + + undef $slots->[$idx]; + # next + } +} + +sub _fmap +{ + my $code = shift; + my %args = @_; + + my $concurrent = $args{concurrent} || 1; + my @slots; + + my $results = []; + my $future = $args{return}; + + my $generator; + if( $generator = $args{generate} ) { + # OK + } + elsif( my $array = $args{foreach} ) { + $generator = sub { return unless @$array; shift @$array }; + } + else { + croak "Expected either 'generate' or 'foreach'"; + } + + # If any of these immediately fail, don't bother continuing + foreach my $idx ( 0 .. $concurrent-1 ) { + $future = _fmap_slot( \@slots, $idx, $code, $generator, $args{collect}, $results, $future ); + last if $future->is_ready; + } + + $future->on_fail( sub { + !defined $_ or $_->is_ready or $_->cancel for @slots; + }); + $future->on_cancel( sub { + $_->cancel for @slots; + }); + + return $future; +} + +=head2 $future = fmap_concat { CODE } ... + +This version of C<fmap> expects each item future to return a list of zero or +more values, and the overall result will be the concatenation of all these +results. It acts like a future-based equivalent to Perl's C<map> operator. + +The results are returned in the order of the original input values, not in the +order their futures complete in. Because of the intermediate storage of +C<ARRAY> references and final flattening operation used to implement this +behaviour, this function is slightly less efficient than C<fmap_scalar> or +C<fmap_void> in cases where item futures are expected only ever to return one, +or zero values, respectively. + +This function is also available under the name of simply C<fmap> to emphasise +its similarity to perl's C<map> keyword. + +=cut + +sub fmap_concat(&@) +{ + my $code = shift; + my %args = @_; + + _fmap( $code, %args, collect => "array" )->then( sub { + return Future->done( map { @$_ } @_ ); + }); +} +*fmap = \&fmap_concat; + +=head2 $future = fmap_scalar { CODE } ... + +This version of C<fmap> acts more like the C<map> functions found in Scheme or +Haskell; it expects that each item future returns only one value, and the +overall result will be a list containing these, in order of the original input +items. If an item future returns more than one value the others will be +discarded. If it returns no value, then C<undef> will be substituted in its +place so that the result list remains in correspondence with the input list. + +This function is also available under the shorter name of C<fmap1>. + +=cut + +sub fmap_scalar(&@) +{ + my $code = shift; + my %args = @_; + + _fmap( $code, %args, collect => "scalar" ) +} +*fmap1 = \&fmap_scalar; + +=head2 $future = fmap_void { CODE } ... + +This version of C<fmap> does not collect any results from its item futures, it +simply waits for them all to complete. Its result future will provide no +values. + +While not a map in the strictest sense, this variant is still useful as a way +to control concurrency of a function call iterating over a list of items, +obtaining its results by some other means (such as side-effects on captured +variables, or some external system). + +This function is also available under the shorter name of C<fmap0>. + +=cut + +sub fmap_void(&@) +{ + my $code = shift; + my %args = @_; + + _fmap( $code, %args, collect => "void" ) +} +*fmap0 = \&fmap_void; + +=head1 AUTHOR + +Paul Evans <leonerd@leonerd.org.uk> + +=cut + +0x55AA; |