1Future::Phrasebook(3) User Contributed Perl DocumentationFuture::Phrasebook(3)
2
3
4

NAME

6       "Future::Phrasebook" - coding examples for "Future" and "Future::Utils"
7
8       This documentation-only module provides a phrasebook-like approach to
9       giving examples on how to use Future and Future::Utils to structure
10       Future-driven asynchronous or concurrent logic. As with any
11       inter-dialect phrasebook it is structured into pairs of examples; each
12       given first in a traditional call/return Perl style, and second in a
13       style using Futures. In each case, the generic function or functions in
14       the example are named in "ALL_CAPITALS()" to make them stand out.
15
16       In the examples showing use of Futures, any function that is expected
17       to return a "Future" instance is named with a leading "F_" prefix. Each
18       example is also constructed so as to yield an overall future in a
19       variable called "$f", which represents the entire operation.
20

SEQUENCING

22       The simplest example of a sequencing operation is simply running one
23       piece of code, then immediately running a second. In call/return code
24       we can just place one after the other.
25
26        FIRST();
27        SECOND();
28
29       Using a Future it is necessary to await the result of the first
30       "Future" before calling the second.
31
32        my $f = F_FIRST()
33           ->then( sub { F_SECOND(); } );
34
35       Here, the anonymous closure is invoked once the "Future" returned by
36       "F_FIRST()" succeeds. Because "then" invokes the code block only if the
37       first Future succeeds, it shortcircuits around failures similar to the
38       way that "die()" shortcircuits around thrown exceptions. A "Future"
39       representing the entire combination is returned by the method.
40
41       Because the "then" method itself returns a "Future" representing the
42       overall operation, it can itself be further chained.
43
44        FIRST();
45        SECOND();
46        THIRD();
47
48        my $f = F_FIRST()
49           ->then( sub { F_SECOND(); } )
50           ->then( sub { F_THIRD(); } );
51
52       See below for examples of ways to handle exceptions.
53
54   Passing Results
55       Often the result of one function can be passed as an argument to
56       another function.
57
58        OUTER( INNER() );
59
60       The result of the first "Future" is passed into the code block given to
61       the "then" method.
62
63        my $f = F_INNER()
64           ->then( sub { F_OUTER( @_ ) } );
65

CONDITIONALS

67       It may be that the result of one function call is used to determine
68       whether or not another operation is taken.
69
70        if( COND() == $value ) {
71           ACTION();
72        }
73
74       Because the "then_with_f" code block is given the first future in
75       addition to its results it can decide whether to call the second
76       function to return a new future, or simply return the one it was given.
77
78        my $f = F_COND()
79           ->then_with_f( sub {
80              my ( $f_cond, $result ) = @_;
81              if( $result == $value ) {
82                 return F_ACTION();
83              }
84              else {
85                 return $f_cond;
86              }
87           });
88

EXCEPTION HANDLING

90       In regular call/return style code, if any function throws an exception,
91       the remainder of the block is not executed, the containing "try" or
92       "eval" is aborted, and control is passed to the corresponding "catch"
93       or line after the "eval".
94
95        try {
96           FIRST();
97        }
98        catch {
99           my $e = $_;
100           ERROR( $e );
101        };
102
103       The "else" method on a "Future" can be used here. It behaves similar to
104       "then", but is only invoked if the initial "Future" fails; not if it
105       succeeds.
106
107        my $f = F_FIRST()
108           ->else( sub { F_ERROR( @_ ); } );
109
110       Alternatively, the second argument to the "then" method can be applied,
111       which is invoked only on case of failure.
112
113        my $f = F_FIRST()
114           ->then( undef, sub { F_ERROR( @_ ); } );
115
116       Often it may be the case that the failure-handling code is in fact
117       immediate, and doesn't return a "Future". In that case, the "else" code
118       block can return an immediate "Future" instance.
119
120        my $f = F_FIRST()
121           ->else( sub {
122              ERROR( @_ );
123              return Future->done;
124           });
125
126       Sometimes the failure handling code simply needs to be aware of the
127       failure, but rethrow it further up.
128
129        try {
130           FIRST();
131        }
132        catch {
133           my $e = $_;
134           ERROR( $e );
135           die $e;
136        };
137
138       In this case, while the "else" block could return a new "Future" failed
139       with the same exception, the "else_with_f" block is passed the failed
140       "Future" itself in addition to the failure details so it can just
141       return that.
142
143        my $f = F_FIRST()
144           ->else_with_f( sub {
145              my ( $f1, @failure ) = @_;
146              ERROR( @failure );
147              return $f1;
148           });
149
150       The "followed_by" method is similar again, though it invokes the code
151       block regardless of the success or failure of the initial "Future". It
152       can be used to create "finally" semantics. By returning the "Future"
153       instance that it was passed, the "followed_by" code ensures it doesn't
154       affect the result of the operation.
155
156        try {
157           FIRST();
158        }
159        catch {
160           ERROR( $_ );
161        }
162        finally {
163           CLEANUP();
164        };
165
166        my $f = F_FIRST()
167           ->else( sub {
168              ERROR( @_ );
169              return Future->done;
170           })
171           ->followed_by( sub {
172              CLEANUP();
173              return shift;
174           });
175

ITERATION

177       To repeat a single block of code multiple times, a "while" block is
178       often used.
179
180        while( COND() ) {
181           FUNC();
182        }
183
184       The "Future::Utils::repeat" function can be used to repeatedly iterate
185       a given "Future"-returning block of code until its ending condition is
186       satisfied.
187
188        use Future::Utils qw( repeat );
189        my $f = repeat {
190           F_FUNC();
191        } while => sub { COND() };
192
193       Unlike the statement nature of perl's "while" block, this "repeat"
194       "Future" can yield a value; the value returned by "$f->get" is the
195       result of the final trial of the code block.
196
197       Here, the condition function it expected to return its result
198       immediately. If the repeat condition function itself returns a
199       "Future", it can be combined along with the loop body. The trial
200       "Future" returned by the code block is passed to the "while" condition
201       function.
202
203        my $f = repeat {
204           F_FUNC()
205              ->followed_by( sub { F_COND(); } );
206        } while => sub { shift->result };
207
208       The condition can be negated by using "until" instead
209
210        until( HALTING_COND() ) {
211           FUNC();
212        }
213
214        my $f = repeat {
215           F_FUNC();
216        } until => sub { HALTING_COND() };
217
218   Iterating with Exceptions
219       Technically, this loop isn't quite the same as the equivalent "while"
220       loop in plain Perl, because the "while" loop will also stop executing
221       if the code within it throws an exception. This can be handled in
222       "repeat" by testing for a failed "Future" in the "until" condition.
223
224        while(1) {
225           TRIAL();
226        }
227
228        my $f = repeat {
229           F_TRIAL();
230        } until => sub { shift->failure };
231
232       When a repeat loop is required to retry a failure, the "try_repeat"
233       function should be used. Currently this function behaves equivalently
234       to "repeat", except that it will not print a warning if it is asked to
235       retry after a failure, whereas this behaviour is now deprecated for the
236       regular "repeat" function so that yields a warning.
237
238        my $f = try_repeat {
239           F_TRIAL();
240        } while => sub { shift->failure };
241
242       Another variation is the "try_repeat_until_success" function, which
243       provides a convenient shortcut to calling "try_repeat" with a condition
244       that makes another attempt each time the previous one fails; stopping
245       once it achieves a successful result.
246
247        while(1) {
248           eval { TRIAL(); 1 } and last;
249        }
250
251        my $f = try_repeat_until_success {
252           F_TRIAL();
253        };
254
255   Iterating over a List
256       A variation on the idea of the "while" loop is the "foreach" loop; a
257       loop that executes once for each item in a given list, with a variable
258       set to one value from that list each time.
259
260        foreach my $thing ( @THINGS ) {
261           INSPECT( $thing );
262        }
263
264       This can be performed with "Future" using the "foreach" parameter to
265       the "repeat" function. When this is in effect, the block of code is
266       passed each item of the given list as the first parameter.
267
268        my $f = repeat {
269           my $thing = shift;
270           F_INSPECT( $thing );
271        } foreach => \@THINGS;
272
273   Recursing over a Tree
274       A regular call/return function can use recursion to walk over a tree-
275       shaped structure, where each item yields a list of child items.
276
277        sub WALK
278        {
279           my ( $item ) = @_;
280           ...
281           WALK($_) foreach CHILDREN($item);
282        }
283
284       This recursive structure can be turned into a "while()"-based repeat
285       loop by using an array to store the remaining items to walk into,
286       instead of using the perl stack directly:
287
288        sub WALK
289        {
290           my @more = ( $root );
291           while( @more ) {
292              my $item = shift @more;
293              ...
294              unshift @more, CHILDREN($item)
295           }
296        }
297
298       This arrangement then allows us to use "fmap_void" to walk this
299       structure using Futures, possibly concurrently. A lexical array
300       variable is captured that holds the stack of remaining items, which is
301       captured by the item code so it can "unshift" more into it, while also
302       being used as the actual "fmap" control array.
303
304        my @more = ( $root );
305
306        my $f = fmap_void {
307           my $item = shift;
308           ...->on_done( sub {
309              unshift @more, @CHILDREN;
310           })
311        } foreach => \@more;
312
313       By choosing to either "unshift" or "push" more items onto this list,
314       the tree can be walked in either depth-first or breadth-first order.
315

SHORT-CIRCUITING

317       Sometimes a result is determined that should be returned through
318       several levels of control structure. Regular Perl code has such
319       keywords as "return" to return a value from a function immediately, or
320       "last" for immediately stopping execution of a loop.
321
322        sub func {
323           foreach my $item ( @LIST ) {
324              if( COND($item) ) {
325                 return $item;
326              }
327           }
328           return MAKE_NEW_ITEM();
329        }
330
331       The "Future::Utils::call_with_escape" function allows this general form
332       of control flow, by calling a block of code that is expected to return
333       a future, and itself returning a future. Under normal circumstances the
334       result of this future propagates through to the one returned by
335       "call_with_escape".
336
337       However, the code is also passed in a future value, called here the
338       "escape future". If the code captures this future and completes it
339       (either by calling "done" or "fail"), then the overall returned future
340       immediately completes with that result instead, and the future returned
341       by the code block is cancelled.
342
343        my $f = call_with_escape {
344           my $escape_f = shift;
345
346           ( repeat {
347              my $item = shift;
348              COND($item)->then( sub {
349                 my ( $result ) = @_;
350                 if( $result ) {
351                    $escape_f->done( $item );
352                 }
353                 return Future->done;
354              })
355           } foreach => \@ITEMS )->then( sub {
356              MAKE_NEW_ITEM();
357           });
358        };
359
360       Here, if $escape_f is completed by the condition test, the future chain
361       returned by the code (that is, the "then" chain of the "repeat" block
362       followed by "MAKE_NEW_ITEM()") will be cancelled, and $f itself will
363       receive this result.
364

CONCURRENCY

366       This final section of the phrasebook demonstrates a number of abilities
367       that are simple to do with "Future" but can't easily be done with
368       regular call/return style programming, because they all involve an
369       element of concurrency. In these examples the comparison with regular
370       call/return code will be somewhat less accurate because of the inherent
371       ability for the "Future"-using version to behave concurrently.
372
373   Waiting on Multiple Functions
374       The "Future->wait_all" constructor creates a "Future" that waits for
375       all of the component futures to complete. This can be used to form a
376       sequence with concurrency.
377
378        { FIRST_A(); FIRST_B() }
379        SECOND();
380
381        my $f = Future->wait_all( FIRST_A(), FIRST_B() )
382           ->then( sub { SECOND() } );
383
384       Unlike in the call/return case, this can perform the work of
385       "FIRST_A()" and "FIRST_B()" concurrently, only proceeding to "SECOND()"
386       when both are ready.
387
388       The result of the "wait_all" "Future" is the list of its component
389       "Future"s. This can be used to obtain the results.
390
391        SECOND( FIRST_A(), FIRST_B() );
392
393        my $f = Future->wait_all( FIRST_A(), FIRST_B() )
394           ->then( sub {
395              my ( $f_a, $f_b ) = @_
396              SECOND( $f_a->result, $f_b->result );
397           } );
398
399       Because the "get" method will re-raise an exception caused by a failure
400       of either of the "FIRST" functions, the second stage will fail if any
401       of the initial Futures failed.
402
403       As this is likely to be the desired behaviour most of the time, this
404       kind of control flow can be written slightly neater using
405       "Future->needs_all" instead.
406
407        my $f = Future->needs_all( FIRST_A(), FIRST_B() )
408           ->then( sub { SECOND( @_ ) } );
409
410       The "get" method of a "needs_all" convergent Future returns a
411       concatenated list of the results of all its component Futures, as the
412       only way it will succeed is if all the components do.
413
414   Waiting on Multiple Calls of One Function
415       Because the "wait_all" and "needs_all" constructors take an entire list
416       of "Future" instances, they can be conveniently used with "map" to wait
417       on the result of calling a function concurrently once per item in a
418       list.
419
420        my @RESULT = map { FUNC( $_ ) } @ITEMS;
421        PROCESS( @RESULT );
422
423       Again, the "needs_all" version allows more convenient access to the
424       list of results.
425
426        my $f = Future->needs_all( map { F_FUNC( $_ ) } @ITEMS )
427           ->then( sub {
428              my @RESULT = @_;
429              F_PROCESS( @RESULT )
430           } );
431
432       This form of the code starts every item's future concurrently, then
433       waits for all of them. If the list of @ITEMS is potentially large, this
434       may cause a problem due to too many items running at once. Instead, the
435       "Future::Utils::fmap" family of functions can be used to bound the
436       concurrency, keeping at most some given number of items running,
437       starting new ones as existing ones complete.
438
439        my $f = fmap {
440           my $item = shift;
441           F_FUNC( $item )
442        } foreach => \@ITEMS;
443
444       By itself, this will not actually act concurrently as it will only keep
445       one Future outstanding at a time. The "concurrent" flag lets it keep a
446       larger number "in flight" at any one time:
447
448        my $f = fmap {
449           my $item = shift;
450           F_FUNC( $item )
451        } foreach => \@ITEMS, concurrent => 10;
452
453       The "fmap" and "fmap_scalar" functions return a Future that will
454       eventually give the collected results of the individual item futures,
455       thus making them similar to perl's "map" operator.
456
457       Sometimes, no result is required, and the items are run in a loop
458       simply for some side-effect of the body.
459
460        foreach my $item ( @ITEMS ) {
461           FUNC( $item );
462        }
463
464       To avoid having to collect a potentially-large set of results only to
465       throw them away, the "fmap_void" function variant of the "fmap" family
466       yields a Future that completes with no result after all the items are
467       complete.
468
469        my $f = fmap_void {
470           my $item = shift;
471           F_FIRST( $item )
472        } foreach => \@ITEMS, concurrent => 10;
473

AUTHOR

475       Paul Evans <leonerd@leonerd.org.uk>
476
477
478
479perl v5.32.1                      2021-01-27             Future::Phrasebook(3)
Impressum