File Coverage

blib/lib/Sub/Curried.pm
Criterion Covered Total %
statement 116 117 99.1
branch 22 26 84.6
condition 4 6 66.6
subroutine 27 28 96.4
pod 0 13 0.0
total 169 190 88.9


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 USAGE
33              
34             Define a curried subroutine using the C keyword. You should list the
35             arguments to the subroutine in parentheses. This isn't a sophisticated signature
36             parser, just a common separated list of scalars (or C<@array> or C<%hash> arguments,
37             which will be returned as a I).
38              
39             curry greet ($greeting, $greetee) {
40             return "$greeting $greetee";
41             }
42              
43             my $hello = greet("Hello");
44             say $hello->("World"); # Hello World
45              
46             =head2 Currying
47              
48             Currying applies the arguments from left to right, returning a more specialised function
49             as it goes until all the arguments are ready, at which point the sub returns its value.
50              
51             curry three ($one,$two,$three) {
52             return $one + $two * $three
53             }
54              
55             three(1,2,3) # normal call - returns 7
56              
57             three(1) # a new subroutine, with $one bound to the number 1
58             ->(2,3) # call the new sub with these arguments
59              
60             three(1)->(2)->(3) # You could call the curried sub like this,
61             # instead of commas (1,2,3)
62              
63             What about calling with I arguments? By extension that would return a function exactly
64             like the original one... but with I arguments prebound (i.e. it's an alias!)
65              
66             my $fn = three; # same as my $fn = \&three;
67              
68             =head2 Anonymous curries
69              
70             Just like you can have anonymous subs, you can have anonymous curried subs:
71              
72             my $greet = curry ($greeting, $greetee) { ... }
73              
74             =head2 Composition
75              
76             Curried subroutines are I. This means that we can create a new
77             subroutine that takes the result of the second subroutine as the input of the
78             first.
79              
80             Let's say we wanted to expand our greeting to add some punctuation at the end:
81              
82             curry append ($r, $l) { $l . $r }
83             curry prepend ($l, $r) { $l . $r }
84              
85             my $ciao = append('!') << prepend('Ciao ');
86             say $ciao->('Bella'); # Ciao Bella!
87              
88             How does this work? Follow the pipeline in the direction of the EE...
89             First we prepend 'Ciao ' to get 'Ciao Bella', then we pass that to the curry that
90             appends '!'. We can also write them in the opposite order, to match evaluation
91             order, by reversing the operator:
92              
93             my $ciao = prepend('Ciao ') >> append('!');
94             say $ciao->('Bella'); # Ciao Bella!
95              
96             Finally, we can create a shell-like pipeline:
97              
98             say 'Bella' | prepend('Ciao ') | append('!'); # Ciao Bella!
99              
100             The overloaded syntax is provided by C which is distributed with
101             this module as a base class.
102              
103             =cut
104              
105             package Sub::Curried;
106 8     8   283842 use base 'Sub::Composable';
  8         18  
  8         3864  
107 8     8   37 use strict; use warnings;
  8     8   16  
  8         185  
  8         41  
  8         11  
  8         172  
108 8     8   35 use Carp 'croak';
  8         14  
  8         530  
109              
110 8     8   7182 use Devel::Declare;
  8         54697  
  8         121  
111 8     8   606 use Sub::Name;
  8         11  
  8         387  
112 8     8   6838 use Sub::Current;
  8         2703  
  8         43  
113 8     8   7159 use B::Hooks::EndOfScope;
  8         118037  
  8         61  
114 8     8   7802 use Devel::BeginLift;
  8         110755  
  8         56  
115              
116             our $VERSION = '0.13';
117              
118             # cargo culted
119             sub import {
120 8     8   82 my $class = shift;
121 8         31 my $caller = caller;
122              
123 8         137 Devel::Declare->setup_for(
124             $caller,
125             { curry => { const => \&parser } }
126             );
127              
128             # would be nice to sugar this
129 8     8   1102 no strict 'refs';
  8         18  
  8         8388  
130 8     0   272 *{$caller.'::curry'} = sub (&) {};
  8         2979  
  0         0  
131             }
132              
133             sub mk_my_var {
134 23     23 0 40 my ($name) = @_;
135 23 50       150 my ($vsigil, $vname) = /^([\$%@])(\w+)$/
136             or die "Bad sigil: $_!"; # not croak, this is in compilation phase
137 23 100       68 my $shift = $vsigil eq '$' ?
138             'shift'
139             : "${vsigil}{+shift}";
140 23         131 return qq[my $vsigil$vname = $shift;];
141             }
142              
143             sub trim {
144 23     23 0 91 s/^\s*//;
145 23         273 s/\s*$//;
146 23         80 $_;
147             }
148             sub get_decl {
149 15   100 15 0 70 my $decl = shift || '';
150 15         68 map trim, split /,/ => $decl;
151             }
152              
153             # Stolen from Devel::Declare's t/method-no-semi.t / Method::Signatures
154             {
155             our ($Declarator, $Offset);
156             sub skip_declarator {
157 15     15 0 55 $Offset += Devel::Declare::toke_move_past_token($Offset);
158             }
159              
160             sub skipspace {
161 45     45 0 120 $Offset += Devel::Declare::toke_skipspace($Offset);
162             }
163              
164             sub strip_name {
165 15     15 0 35 skipspace;
166 15 100       77 if (my $len = Devel::Declare::toke_scan_word($Offset, 1)) {
167 13         44 my $linestr = Devel::Declare::get_linestr();
168 13         38 my $name = substr($linestr, $Offset, $len);
169 13         35 substr($linestr, $Offset, $len) = '';
170 13         39 Devel::Declare::set_linestr($linestr);
171 13         34 return $name;
172             }
173 2         4 return;
174             }
175              
176             sub strip_proto {
177 15     15 0 29 skipspace;
178            
179 15         40 my $linestr = Devel::Declare::get_linestr();
180 15 100       55 if (substr($linestr, $Offset, 1) eq '(') {
181 14         111 my $length = Devel::Declare::toke_scan_str($Offset);
182 14         44 my $proto = Devel::Declare::get_lex_stuff();
183 14         59 Devel::Declare::clear_lex_stuff();
184 14         35 $linestr = Devel::Declare::get_linestr();
185 14         27 substr($linestr, $Offset, $length) = '';
186 14         34 Devel::Declare::set_linestr($linestr);
187 14         31 return $proto;
188             }
189 1         2 return;
190             }
191              
192             sub shadow {
193 15     15 0 40 my $pack = Devel::Declare::get_curstash_name;
194 15         105 Devel::Declare::shadow_sub("${pack}::${Declarator}", $_[0]);
195             }
196              
197             sub inject_if_block {
198 15     15 0 32 my $inject = shift;
199 15         29 skipspace;
200 15         38 my $linestr = Devel::Declare::get_linestr;
201 15 50       57 if (substr($linestr, $Offset, 1) eq '{') {
202 15         43 substr($linestr, $Offset+1, 0) = $inject;
203 15         46 Devel::Declare::set_linestr($linestr);
204             }
205             }
206              
207             sub check_args {
208 50     50 0 13400 my ($name, $exp, $actual) = @_;
209 50 100       243 die "$name, expected $exp args but got $actual" if $actual>$exp;
210             }
211              
212             sub parser {
213 15     15 0 24372 local ($Declarator, $Offset) = @_;
214 15         41 skip_declarator;
215 15         56 my $name = strip_name;
216 15         37 my $proto = strip_proto;
217              
218 15         43 my @decl = get_decl($proto);
219              
220             # We nest each layer of currying in its own sub.
221             # if we were passed more than one argument, then we call more than one layer.
222             # We use the closing brace '}' trick as per monads, but also place the calling
223             # logic here.
224              
225             my $exp_check = sub {
226 15     15   25 my $exp= scalar @decl;
227             sub {
228 23 100       60 my $name = $name ? qq('$name') : 'undef';
229 23         60 my $ret = qq[ Sub::Curried::check_args($name,$exp,scalar \@_); ];
230 23         30 $exp--; return $ret;
  23         92  
231             }
232 15         84 }->();
  15         93  
233              
234             my $installer = sub (&) {
235 15     15   367 my $f = shift;
236 15         37 bless $f, __PACKAGE__;
237 15 100       44 if ($name) {
238 8     8   48 no strict 'refs';
  8         22  
  8         3656  
239 13         122 *{$name} = subname $name => $f;
  13         74  
240             ()
241 13         16394 } else {
242 2         10 $f;
243             }
244 15         130 };
245 15         41 my $si = scope_injector_call(', "Sub::Curried"; ($f,@r)=$f->($_) for @_; wantarray ? ($f,@r) : $f}');
246            
247 23         43 my $inject = (@decl ? 'return Sub::Current::ROUTINE unless @_;' : '')
248             . join qq[ my \@r; my \$f = bless sub { $si; ],
249             map {
250 15 100       70 $exp_check->() . mk_my_var($_);
251             } @decl;
252              
253 15 100       61 if (defined $name) {
254 13 50       250 my $lift_id = Devel::BeginLift->setup_for_cv($installer) if $name;
255              
256 13         47 $inject = scope_injector_call(";Devel::BeginLift->teardown_for_cv($lift_id);").$inject;
257             }
258              
259 15         51 inject_if_block($inject);
260              
261 15 100       40 if (defined $name) {
262 13 50       79 $name = join('::', Devel::Declare::get_curstash_name(), $name)
263             unless ($name =~ /::/);
264             }
265              
266 15         40 shadow($installer);
267             }
268              
269             # Set up the parser scoping hacks that allow us to omit the final
270             # semicolon
271             sub scope_injector_call {
272 28     28 0 43 my $pkg = __PACKAGE__;
273 28   50     85 my $what = shift || ';';
274 28         213 return " BEGIN { B::Hooks::EndOfScope::on_scope_end { ${pkg}::add_at_end_of_scope('$what') } }; ";
275             }
276             sub add_at_end_of_scope {
277 23   50 23 0 4505 my $what = shift || ';';
278 23         84 my $linestr = Devel::Declare::get_linestr;
279 23         50 my $offset = Devel::Declare::get_linestr_offset;
280 23         54 substr($linestr, $offset, 0) = $what;
281 23         93 Devel::Declare::set_linestr($linestr);
282             }
283             }
284              
285              
286             =head1 BUGS
287              
288             No major bugs currently open. Please report any bugs via RT or email, or ping
289             me on IRC (osfameron on irc.perl.org and freenode)
290              
291             =head1 SEE ALSO
292              
293             L provides the magic (yes, there's a teeny bit of code
294             generation involved, but it's not a global filter, rather a localised
295             parsing hack).
296              
297             There are several modules on CPAN that already do currying or partial evaluation:
298              
299             =over 4
300              
301             =item *
302              
303             L - Filter based module prototyping the Perl 6 system
304              
305             =item *
306              
307             L - seems rather complex, with concepts like blackholes and antispices. Odd.
308              
309             =item *
310              
311             L - creates a currying variant of all existing subs automatically. Very odd.
312              
313             =item *
314              
315             L - partial evaluation with named arguments (as hash keys). Has some
316             great debugging hooks (the function is a blessed object which displays what the current
317             bound keys are).
318              
319             =item *
320              
321             L - exactly what we want minus the sugar. (The attribute has
322             to declare how many arguments it's expecting)
323              
324             =back
325              
326             =head1 AUTHOR and LICENSE
327              
328             (c)2008-2013 osfameron@cpan.org
329              
330             =head2 CONTRIBUTORS
331              
332             =over 4
333              
334             =item *
335              
336             Florian (rafl) Ragwitz
337              
338             =item *
339              
340             Paul (prj) Jarc
341              
342             =back
343              
344             This module is distributed under the same terms and conditions as Perl itself.
345              
346             Please submit bugs to RT or shout at me on IRC (osfameron on #london.pm on irc.perl.org)
347              
348             A git repo is available at L
349              
350             =cut
351              
352             1;