File Coverage

blib/lib/Sub/Curried.pm
Criterion Covered Total %
statement 44 48 91.6
branch 18 20 90.0
condition n/a
subroutine 12 13 92.3
pod 0 3 0.0
total 74 84 88.1


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Sub::Curried - automatically curried subroutines
4              
5             =head1 SYNOPSIS
6              
7             curry add_n_to ($n, $val) {
8             return $n+$val;
9             }
10              
11             my $add_10_to = add_n_to( 10 );
12              
13             say $add_10_to->(4); # 14
14              
15             # but you can also
16             say add_n_to(10,4); # also 14
17              
18             # or more traditionally
19             say add_n_to(10)->(4);
20              
21             =head1 DESCRIPTION
22              
23             Currying and Partial Application come from the heady world of functional
24             programming, but are actually useful techniques. Partial Application is used
25             to progressively specialise a subroutine, by pre-binding some of the arguments.
26              
27             Partial application is the generic term, that also encompasses the concept of
28             plugging in "holes" in arguments at arbitrary positions. Currying is more
29             specifically the application of arguments progressively from left to right
30             until you have enough of them.
31              
32             =head1 DEPENDENCIES
33              
34             Beyond those listed in META.yml/META.json, there is an optional dependency on
35             PPR: if you have it installed, then your curry definitions can include POD
36             syntax anywhere whitespace can occur between C and C<{>. Without PPR,
37             that will trigger a syntax error.
38              
39             If your Perl is older than 5.16, you'll also need Sub::Current.
40              
41             =head1 USAGE
42              
43             Define a curried subroutine using the C keyword. You should list the
44             arguments to the subroutine in parentheses. This isn't a sophisticated signature
45             parser, just a common separated list of scalars (or C<@array> or C<%hash> arguments,
46             which will be returned as a I).
47              
48             curry greet ($greeting, $greetee) {
49             return "$greeting $greetee";
50             }
51              
52             my $hello = greet("Hello");
53             say $hello->("World"); # Hello World
54              
55             =head2 Currying
56              
57             Currying applies the arguments from left to right, returning a more specialised function
58             as it goes until all the arguments are ready, at which point the sub returns its value.
59              
60             curry three ($one,$two,$three) {
61             return $one + $two * $three
62             }
63              
64             three(1,2,3) # normal call - returns 7
65              
66             three(1) # a new subroutine, with $one bound to the number 1
67             ->(2,3) # call the new sub with these arguments
68              
69             three(1)->(2)->(3) # You could call the curried sub like this,
70             # instead of commas (1,2,3)
71              
72             What about calling with I arguments? By extension that would return a function exactly
73             like the original one... but with I arguments prebound (i.e. it's an alias!)
74              
75             my $fn = three; # same as my $fn = \&three;
76              
77             =head2 Anonymous curries
78              
79             Just like you can have anonymous subs, you can have anonymous curried subs:
80              
81             my $greet = curry ($greeting, $greetee) { ... }
82              
83             =head2 Composition
84              
85             Curried subroutines are I. This means that we can create a new
86             subroutine that takes the result of the second subroutine as the input of the
87             first.
88              
89             Let's say we wanted to expand our greeting to add some punctuation at the end:
90              
91             curry append ($r, $l) { $l . $r }
92             curry prepend ($l, $r) { $l . $r }
93              
94             my $ciao = append('!') << prepend('Ciao ');
95             say $ciao->('Bella'); # Ciao Bella!
96              
97             How does this work? Follow the pipeline in the direction of the EE...
98             First we prepend 'Ciao ' to get 'Ciao Bella', then we pass that to the curry that
99             appends '!'. We can also write them in the opposite order, to match evaluation
100             order, by reversing the operator:
101              
102             my $ciao = prepend('Ciao ') >> append('!');
103             say $ciao->('Bella'); # Ciao Bella!
104              
105             Finally, we can create a shell-like pipeline:
106              
107             say 'Bella' | prepend('Ciao ') | append('!'); # Ciao Bella!
108              
109             The overloaded syntax is provided by C which is distributed with
110             this module as a base class.
111              
112             =head2 Argument aliasing
113              
114             When all the arguments are supplied and the function body is executed, the
115             arguments values are available in both the named parameters and the C<@_>
116             array. Just as in a normal subroutine call, the elements of C<@_> (but
117             I the named parameters) are aliased to the variables supplied by the
118             caller, so you can use pass-by-reference semantics.
119              
120             curry set ($a, $b) {
121             foreach my $arg (@_) { $arg = 1; } # affects the caller
122             $a = $b = 2; # doesn't affect the caller
123             }
124             my ($x, $y) = (0, 0);
125             set($x)->($y); # $x == 1, $y == 1
126              
127             =head2 Stack traces
128              
129             The innermost stack frame has the function name you defined, with all the
130             accumulated arguments. Any intermediate stack frames have the same or
131             similar function names; currently there is a C<__curried> suffix, but that
132             may change in the future. Currently there is only one intermediate stack
133             frame, showing just the arguments that were passed in the final call that
134             reached the required number of arguments, but that may change in the future.
135             If you supply all the arguments in one call, there are no intermediate stack
136             frames.
137              
138             use Carp 'confess';
139             curry func ($a, $b, $c, $d) {
140             confess('ERROR MESSAGE');
141             }
142             sub call {
143             func(1)->(2)->(3, 4);
144             }
145             call();
146              
147             ERROR MESSAGE at script.pl line 3
148             main::func(1, 2, 3, 4) called at .../Sub/Curried.pm line 202
149             main::func__curried(3, 4) called at script.pl line 6
150             main::call() called at script.pl line 8
151              
152             =cut
153              
154 10     10   1158263 use strict; use warnings;
  10     10   19  
  10         386  
  10         90  
  10         18  
  10         887  
155             package Sub::Curried;
156             $Sub::Curried::VERSION = '0.14';
157 10     10   4749 use parent 'Sub::Composable';
  10         3318  
  10         61  
158              
159 10     10   579 use Sub::Name;
  10         21  
  10         704  
160 10     10   4705 use Keyword::Pluggable 1.05;
  10         24862  
  10         378  
161 10     10   5659 use Attribute::Handlers;
  10         75413  
  10         78  
162              
163             sub import {
164 10     10   185 Keyword::Pluggable::define('keyword' => 'curry',
165             'code' => \&injector,
166             'expression' => 'dynamic');
167             }
168              
169             sub unimport {
170 0     0   0 Keyword::Pluggable::undefine('keyword' => 'curry');
171             }
172              
173             sub UNIVERSAL::Sub__Curried :ATTR(CODE) {
174 17     17 0 33374 my ($package, $symbol, $ref, $attr, $arg) = @_;
175 17         74 bless($ref, __PACKAGE__);
176 10     10   1650 }
  10         22  
  10         49  
177              
178             my $current_sub;
179             BEGIN {
180 10 50   10   4741 if ($^V lt v5.16.0) {
181 0         0 require Sub::Current;
182 0         0 $current_sub = 'Sub::Current::ROUTINE';
183             } else {
184 10         10804 $current_sub = 'CORE::__SUB__';
185             }
186             }
187              
188             # PPR is the easiest way to parse POD. But POD between "curry" and "{" was
189             # never supported before, and PPR may be slow depending on the Perl version,
190             # so make it optional.
191             eval { require PPR; };
192             my $space = qr/(?:\s|#[^\n]*\n)/;
193             my $ppr = exists($INC{"PPR/pm"})? $PPR::GRAMMAR: '';
194             my $nspace = exists($INC{"PPR/pm"})? '(?&PerlNWS)': qr/$space+/;
195             my $ospace = exists($INC{"PPR/pm"})? '(?&PerlOWS)': qr/$space*/;
196             my $sigil = qr/[\$\%\@]/;
197             my $ident = qr/(?:\p{XIDS}\p{XIDC}*)/;
198             my $param = qr/$ospace $sigil $ident/x;
199              
200             sub injector {
201 17     17 0 377831 my ($text) = @_;
202 17 50       4006 if ($$text !~ s/\A
203             (? $nspace (?$ident))?
204             (? $ospace \(
205             (? $param (?: $ospace , $param)* )?
206             $ospace \) )?
207             (? $ospace ) \{ $ppr
208 17         317 /injection(%+)/xe) {
209 0         0 die('invalid Sub::Curried syntax: '.substr($$text, 0, 80).'...');
210             }
211 17         4093 return !defined($+{'name'});
212             }
213              
214             sub injection {
215 17     17 0 184 my (%match) = @_;
216 17         50 my $esc_name = $match{'name'};
217 17 100       68 if (defined($esc_name)) { $esc_name =~ s/([\\'])/\\$1/g; }
  15         48  
218             my $curried_name = (defined($match{'name'})
219 17 100       100 ? $match{'name'} . '__curried'
220             : undef);
221 17         30 my $esc_curried_name = $curried_name;
222 17 100       96 if (defined($esc_curried_name)) { $esc_curried_name =~ s/([\\'])/\\$1/g; }
  15         34  
223 17 100       99 my @name_wrapper = (defined($curried_name)
224             ? ("Sub::Name::subname('".$esc_curried_name."', ", ")")
225             : ('', ''));
226             my @params = (defined($match{'params'})
227 17 100       46 ? @{[ ($match{'params'}.',') =~
  15         1490  
228             m/$ospace ($sigil $ident) $ospace,$ppr/gx ]}
229             : ());
230             return join('',
231             'sub', grep(defined($_), $match{'spacename'}),
232             ' :Sub__Curried', $match{'space'}, '{',
233             ' if (@_ > ', scalar(@params), ') {',
234             " die('", (defined($esc_name)
235             ? $esc_name
236             : ''),
237             ", expected ", scalar(@params),
238             " args but got '.\@_);",
239             ' }',
240             (@params == 0
241             ? () # We never need to return a closure
242             : (
243             ' if (@_ < ', scalar(@params), ') {',
244             ' my $func = ', $current_sub, ';',
245             ' my $args = \@_;',
246             ' return ',
247             $name_wrapper[0],
248             'bless(sub { $func->(@$args, @_) }, "Sub::Curried")',
249             $name_wrapper[1],
250             ';',
251             ' }')),
252 17 100       190 map({ my @param = ('$_[', $_, ']');
  30 100       68  
253 30 100       354 (' my ', $params[$_], ' = ',
254             ($params[$_]=~/^([\%\@])/
255             ? ($1, '{', @param, '}')
256             : @param), ';') }
257             0..$#params));
258             }
259              
260             =head1 BUGS
261              
262             No major bugs currently open. Please report any bugs via RT or email.
263              
264             =head1 SEE ALSO
265              
266             L provides the syntactic magic.
267              
268             There are several modules on CPAN that already do currying or partial evaluation:
269              
270             =over 4
271              
272             =item *
273              
274             L - Filter based module prototyping the Perl 6 system
275              
276             =item *
277              
278             L - seems rather complex, with concepts like blackholes and antispices. Odd.
279              
280             =item *
281              
282             L - creates a currying variant of all existing subs automatically. Very odd.
283              
284             =item *
285              
286             L - partial evaluation with named arguments (as hash keys). Has some
287             great debugging hooks (the function is a blessed object which displays what the current
288             bound keys are).
289              
290             =item *
291              
292             L - exactly what we want minus the sugar. (The attribute has
293             to declare how many arguments it's expecting)
294              
295             =back
296              
297             =head1 AUTHOR
298              
299             (c)2008-2013 osfameron@cpan.org
300             (c)2024 Paul Jarc
301              
302             =head1 CONTRIBUTORS
303              
304             =over 4
305              
306             =item *
307              
308             Florian (rafl) Ragwitz
309              
310             =back
311              
312             =head1 LICENSE
313              
314             This module is distributed under the same terms and conditions as Perl itself.
315              
316             =head1 CONTRIBUTING
317              
318             Please submit bugs to RT or email.
319              
320             A git repo is available at L
321              
322             =cut
323              
324             1;