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
"Future" before calling the second.
my $f = F_FIRST()
->then( sub { F_SECOND(); } );
Here, the anonymous closure is invoked once the
"Future" returned by
"F_FIRST()" succeeds. Because
"then" invokes the code block only if the
first Future succeeds, it shortcircuits around failures similar to the way
that "die()" shortcircuits around thrown
exceptions. A "Future" representing the
entire combination is returned by the method.
Because the "then" method itself
returns a "Future" representing the
overall operation, it can itself be further chained.
FIRST();
SECOND();
THIRD();
my $f = F_FIRST()
->then( sub { F_SECOND(); } )
->then( sub { F_THIRD(); } );
See below for examples of ways to handle exceptions.
Often the result of one function can be passed as an argument to
another function.
OUTER( INNER() );
The result of the first "Future"
is passed into the code block given to the
"then" method.
my $f = F_INNER()
->then( sub { F_OUTER( @_ ) } );
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 "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;
}
});
In regular call/return style code, if any function throws an
exception, the remainder of the block is not executed, the containing
"try" or
"eval" is aborted, and control is passed
to the corresponding "catch" or line after
the "eval".
try {
FIRST();
}
catch {
my $e = $_;
ERROR( $e );
};
The "else" method on a
"Future" can be used here. It behaves
similar to "then", but is only invoked if
the initial "Future" fails; not if it
succeeds.
my $f = F_FIRST()
->else( sub { F_ERROR( @_ ); } );
Alternatively, the second argument to the
"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 "Future".
In that case, the "else" code block can
return an immediate "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 "else"
block could return a new "Future" failed
with the same exception, the "else_with_f"
block is passed the failed "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 "followed_by" method is
similar again, though it invokes the code block regardless of the success or
failure of the initial "Future". It can be
used to create "finally" semantics. By
returning the "Future" instance that it
was passed, the "followed_by" code ensures
it doesn't affect the result of the operation.
try {
FIRST();
}
catch {
ERROR( $_ );
}
finally {
CLEANUP();
};
my $f = F_FIRST()
->else( sub {
ERROR( @_ );
return Future->done;
})
->followed_by( sub {
CLEANUP();
return shift;
});
To repeat a single block of code multiple times, a
"while" block is often used.
while( COND() ) {
FUNC();
}
The "Future::Utils::repeat"
function can be used to repeatedly iterate a given
"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
"while" block, this
"repeat"
"Future" can yield a value; the value
returned by "$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
"Future", it can be combined along with
the loop body. The trial "Future" returned
by the code block is passed to the "while"
condition function.
my $f = repeat {
F_FUNC()
->followed_by( sub { F_COND(); } );
} while => sub { shift->get };
The condition can be negated by using
"until" instead
until( HALTING_COND() ) {
FUNC();
}
my $f = repeat {
F_FUNC();
} until => sub { HALTING_COND() };
Technically, this loop isn't quite the same as the equivalent
"while" loop in plain Perl, because the
"while" loop will also stop executing if
the code within it throws an exception. This can be handled in
"repeat" by testing for a failed
"Future" in the
"until" condition.
while(1) {
TRIAL();
}
my $f = repeat {
F_TRIAL();
} until => sub { shift->failure };
When a repeat loop is required to retry a failure, the
"try_repeat" function should be used.
Currently this function behaves equivalently to
"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 "repeat"
function so that yields a warning.
my $f = try_repeat {
F_TRIAL();
} while => sub { shift->failure };
Another variation is the
"try_repeat_until_success" function, which
provides a convenient shortcut to calling
"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;
}
my $f = try_repeat_until_success {
F_TRIAL();
};
A variation on the idea of the
"while" loop is the
"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
"Future" using the
"foreach" parameter to the
"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;
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
"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
"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 "unshift" more into it, while also
being used as the actual "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 "unshift"
or "push" more items onto this list, the
tree can be walked in either depth-first or breadth-first order.
Sometimes a result is determined that should be returned through
several levels of control structure. Regular Perl code has such keywords as
"return" to return a value from a function
immediately, or "last" for immediately
stopping execution of a loop.
sub func {
foreach my $item ( @LIST ) {
if( COND($item) ) {
return $item;
}
}
return MAKE_NEW_ITEM();
}
The
"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 "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 "done" or
"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 $escape_f is completed by the
condition test, the future chain returned by the code (that is, the
"then" chain of the
"repeat" block followed by
"MAKE_NEW_ITEM()") will be cancelled, and
$f itself will receive this result.
This final section of the phrasebook demonstrates a number of
abilities that are simple to do with
"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
"Future"-using version to behave
concurrently.
The "Future->wait_all"
constructor creates a "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();
my $f = Future->wait_all( FIRST_A(), FIRST_B() )
->then( sub { SECOND() } );
Unlike in the call/return case, this can perform the work of
"FIRST_A()" and
"FIRST_B()" concurrently, only proceeding
to "SECOND()" when both are ready.
The result of the "wait_all"
"Future" is the list of its component
"Future"s. This can be used to obtain the
results.
SECOND( FIRST_A(), FIRST_B() );
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 "get" method will
re-raise an exception caused by a failure of either of the
"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
"Future->needs_all" instead.
my $f = Future->needs_all( FIRST_A(), FIRST_B() )
->then( sub { SECOND( @_ ) } );
The "get" method of a
"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.
Because the "wait_all" and
"needs_all" constructors take an entire
list of "Future" instances, they can be
conveniently used with "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 "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 @ITEMS is
potentially large, this may cause a problem due to too many items running at
once. Instead, the "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
"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 "fmap" and
"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
"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 "fmap_void"
function variant of the "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;
Paul Evans <leonerd@leonerd.org.uk>