1Future::Phrasebook(3) User Contributed Perl DocumentationFuture::Phrasebook(3)
2
3
4
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
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
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
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
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
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
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
475 Paul Evans <leonerd@leonerd.org.uk>
476
477
478
479perl v5.34.0 2022-01-28 Future::Phrasebook(3)