1CPS::Functional(3) User Contributed Perl Documentation CPS::Functional(3)
2
3
4
6 "CPS::Functional" - functional utilities in Continuation-Passing Style
7
9 use CPS::Functional qw( kmap );
10
11 use Example::HTTP::Client qw( k_get_http );
12 use List::Util qw( sum );
13
14 my @URLs = (
15 "http://www.foo.com",
16 "http://www.bar.com",
17 );
18
19 kmap( \@URLs,
20 sub {
21 my ( $item, $kret ) = @_;
22
23 k_get_http( uri => $item, on_response => sub {
24 my ( $response ) = @_;
25
26 $kret->( $response->content_length );
27 } );
28 },
29 sub {
30 my ( @sizes ) = @_;
31
32 say "Total length of all URLs: " . sum(@sizes);
33 },
34 );
35
37 This module provides CPS versions of data-flow functionals, such as
38 Perl's "map" and "grep", where function bodies are invoked and expected
39 to return data, which the functional manages. They are built on top of
40 the control-flow functionals provided by the "CPS" module itself.
41
43 kmap( \@items, \&body, $k )
44 CPS version of perl's "map" statement. Calls the "body" code once for
45 each element in @items, capturing the list of values the body passes
46 into its continuation. When the items are exhausted, $k is invoked and
47 passed a list of all the collected values.
48
49 $body->( $item, $kret )
50 $kret->( @items_out )
51
52 $k->( @all_items_out )
53
54 kgrep( \@items, \&body, $k )
55 CPS version of perl's "grep" statement. Calls the "body" code once for
56 each element in @items, capturing those elements where the body's
57 continuation was invoked with a true value. When the items are
58 exhausted, $k is invoked and passed a list of the subset of @items
59 which were selected.
60
61 $body->( $item, $kret )
62 $kret->( $select )
63
64 $k->( @chosen_items )
65
66 kfoldl( \@items, \&body, $k )
67 CPS version of "List::Util::reduce", which collapses (or "folds") a
68 list of values down to a single scalar, by successively accumulating
69 values together.
70
71 If @items is empty, invokes $k immediately, passing in "undef".
72
73 If @items contains a single value, invokes $k immediately, passing in
74 just that single value.
75
76 Otherwise, initialises an accumulator variable with the first value in
77 @items, then for each additional item, invokes the "body" passing in
78 the accumulator and the next item, storing back into the accumulator
79 the value that "body" passed to its continuation. When the @items are
80 exhausted, it invokes $k, passing in the final value of the
81 accumulator.
82
83 $body->( $acc, $item, $kret )
84 $kret->( $new_acc )
85
86 $k->( $final_acc )
87
88 Technically, this is not a true Scheme/Haskell-style "foldl", as it
89 does not take an initial value. (It is what Haskell calls "foldl1".)
90 However, if such an initial value is required, this can be provided by
91
92 kfoldl( [ $initial, @items ], \&body, $k )
93
94 kfoldr( \@items, \&body, $k )
95 A right-associative version of kfoldl(). Where kfoldl() starts with the
96 first two elements in @items and works forward, kfoldr() starts with
97 the last two and works backward.
98
99 $body->( $item, $acc, $kret )
100 $kret->( $new_acc )
101
102 $k->( $final_acc )
103
104 As before, an initial value can be provided by modifying the @items
105 array, though note it has to be last this time:
106
107 kfoldr( [ @items, $initial ], \&body, $k )
108
109 kunfold( $seed, \&body, $k )
110 An inverse operation to kfoldl(); turns a single scalar into a list of
111 items. Repeatedly calls the "body" code, capturing the values it
112 returns, until it indicates the end of the loop, then invoke $k with
113 the collected values.
114
115 $body->( $seed, $kmore, $kdone )
116 $kmore->( $new_seed, @items )
117 $kdone->( @items )
118
119 $k->( @all_items )
120
121 With each iteration, the "body" is invoked and passed the current $seed
122 value and two continuations, $kmore and $kdone. If $kmore is invoked,
123 the passed items, if any, are appended to the eventual result list. The
124 "body" is then re-invoked with the new $seed value. If $klast is
125 invoked, the passed items, if any, are appended to the return list,
126 then the entire list is passed to $k.
127
129 The following aren't necessarily examples of code which would be found
130 in real programs, but instead, demonstrations of how to use the above
131 functions as ways of controlling program flow.
132
133 Without dragging in large amount of detail on an asynchronous or event-
134 driven framework, it is difficult to give a useful example of behaviour
135 that CPS allows that couldn't be done just as easily without.
136 Nevertheless, I hope the following examples will be useful to
137 demonstrate use of the above functions, in a way which hints at their
138 use in a real program.
139
140 Implementing join() using kfoldl()
141 use CPS::Functional qw( kfoldl );
142
143 my @words = qw( My message here );
144
145 kfoldl(
146 \@words,
147 sub {
148 my ( $left, $right, $k ) = @_;
149
150 $k->( "$left $right" );
151 },
152 sub {
153 my ( $str ) = @_;
154
155 print "Joined up words: $str\n";
156 }
157 );
158
159 Implementing split() using kunfold()
160 The following program illustrates the way that kunfold() can split a
161 string, in a reverse way to the way kfoldl() can join it.
162
163 use CPS::Functional qw( kunfold );
164
165 my $str = "My message here";
166
167 kunfold(
168 $str,
169 sub {
170 my ( $s, $kmore, $kdone ) = @_;
171
172 if( $s =~ s/^(.*?) // ) {
173 return $kmore->( $s, $1 );
174 }
175 else {
176 return $kdone->( $s );
177 }
178 },
179 sub {
180 my @words = @_;
181 print "Words in message:\n";
182 print "$_\n" for @words;
183 }
184 );
185
186 Generating Prime Numbers
187 While the design of kunfold() is symmetric to kfoldl(), the seed value
188 doesn't have to be successively broken apart into pieces. Another valid
189 use for it may be storing intermediate values in computation, such as
190 in this example, storing a list of known primes, to help generate the
191 next one:
192
193 use CPS::Functional qw( kunfold );
194
195 kunfold(
196 [ 2, 3 ],
197 sub {
198 my ( $vals, $kmore, $kdone ) = @_;
199
200 return $kdone->() if @$vals >= 50;
201
202 PRIME: for( my $n = $vals->[-1] + 2; ; $n += 2 ) {
203 $n % $_ == 0 and next PRIME for @$vals;
204
205 push @$vals, $n;
206 return $kmore->( $vals, $n );
207 }
208 },
209 sub {
210 my @primes = ( 2, 3, @_ );
211 print "Primes are @primes\n";
212 }
213 );
214
215 Forward-reading Program Flow
216 One side benefit of the CPS control-flow methods which is unassociated
217 with asynchronous operation, is that the flow of data reads in a more
218 natural left-to-right direction, instead of the right-to-left flow in
219 functional style. Compare
220
221 sub square { $_ * $_ }
222 sub add { $a + $b }
223
224 print reduce( \&add, map( square, primes(10) ) );
225
226 (because "map" is a language builtin but "reduce" is a function with
227 "(&)" prototype, it has a different way to pass in the named functions)
228
229 with
230
231 my $ksquare = liftk { $_[0] * $_[0] };
232 my $kadd = liftk { $_[0] + $_[1] };
233
234 kprimes 10, sub {
235 kmap \@_, $ksquare, sub {
236 kfoldl \@_, $kadd, sub {
237 print $_[0];
238 }
239 }
240 };
241
242 This translates roughly to a functional vs imperative way to describe
243 the problem:
244
245 Print the sum of the squares of the first 10 primes.
246
247 Take the first 10 primes. Square them. Sum them. Print.
248
249 Admittedly the closure creation somewhat clouds the point in this small
250 example, but in a larger example, the real problem-solving logic would
251 be larger, and stand out more clearly against the background
252 boilerplate.
253
255 • CPS - manage flow of control in Continuation-Passing Style
256
258 Paul Evans <leonerd@leonerd.org.uk>
259
260
261
262perl v5.36.0 2023-01-20 CPS::Functional(3)