1CPS::Functional(3)    User Contributed Perl Documentation   CPS::Functional(3)
2
3
4

NAME

6       "CPS::Functional" - functional utilities in Continuation-Passing Style
7

SYNOPSIS

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

DESCRIPTION

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

FUNCTIONS

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
96       the first two elements in @items and works forward, "kfoldr()" starts
97       with 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
111       of 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

EXAMPLES

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
188       value doesn't have to be successively broken apart into pieces. Another
189       valid use for it may be storing intermediate values in computation,
190       such as in this example, storing a list of known primes, to help
191       generate the 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

SEE ALSO

255       •   CPS - manage flow of control in Continuation-Passing Style
256

AUTHOR

258       Paul Evans <leonerd@leonerd.org.uk>
259
260
261
262perl v5.34.0                      2021-07-22                CPS::Functional(3)
Impressum