File Coverage

blib/lib/Sub/DeferredPartial.pm
Criterion Covered Total %
statement 51 68 75.0
branch 3 12 25.0
condition 3 7 42.8
subroutine 13 16 81.2
pod 1 8 12.5
total 71 111 63.9


line stmt bran cond sub pod time code
1             package Sub::DeferredPartial;
2              
3             our $VERSION = '0.01';
4              
5 2     2   30615 use Sub::DeferredPartial::Attributes();
  2         6  
  2         36  
6 2     2   988 use Sub::DeferredPartial::Op::Nullary();
  2         5  
  2         37  
7 2     2   1058 use Sub::DeferredPartial::Op::Unary();
  2         5  
  2         45  
8 2     2   1175 use Sub::DeferredPartial::Op::Binary();
  2         4  
  2         44  
9 2     2   11 use Carp;
  2         4  
  2         140  
10              
11             use overload
12 2         12 '&{}' => 'Subify'
13             , '""' => 'Describe'
14             , nomethod => 'NoMethod'
15 2     2   3225 ;
  2         2047  
16             # -----------------------------------------------------------------------------
17             sub import
18             # -----------------------------------------------------------------------------
19             {
20 2     2   16 my $class = shift;
21 2   100     13 my $Name = shift || 'defer';
22 2         4 my $Caller = caller;
23              
24 2         4 *{"$Caller\::$Name"} = \&Defer;
  2         11  
25 2         15 Sub::DeferredPartial::Attributes->import( $Caller );
26             }
27             # -----------------------------------------------------------------------------
28             sub new
29             # -----------------------------------------------------------------------------
30             {
31 1     1 0 3 my $class = shift;
32 1         3 my $Sub = shift;
33 1         2 my $Free = shift;
34 1   50     12 my $Bound = shift || {};
35              
36 1         10 bless { Sub => $Sub, F => $Free, B => $Bound } => $class;
37             }
38             # -----------------------------------------------------------------------------
39             sub Subify
40             # -----------------------------------------------------------------------------
41             {
42 1     1 0 652 my $self = shift;
43              
44 1 50   1   8 return sub { return @_ ? $self->Apply( @_ ) : $self->Eval };
  1         7  
45             }
46             # -----------------------------------------------------------------------------
47             sub Apply
48             # -----------------------------------------------------------------------------
49             {
50 1     1 0 3 my $self = shift;
51 1         6 my %Args = @_;
52 1         2 my %F = %{$self->{F}};
  1         4  
53 1         2 my %B = %{$self->{B}};
  1         3  
54              
55 1         7 while ( my ( $k, $v ) = each %Args )
56             {
57 1 50       4 confess "Bound parameter: $k" if exists $B{$k}; $B{$k} = $v;
  1         2  
58 1 50       316 confess "Wrong parameter: $k" unless exists $F{$k}; delete $F{$k};
  0         0  
59             }
60 0         0 return ref( $self )->new( $self->{Sub}, \%F, \%B );
61             }
62             # -----------------------------------------------------------------------------
63             sub Eval
64             # -----------------------------------------------------------------------------
65             {
66 0     0 0 0 my $self = shift;
67              
68 0         0 confess "Free parameter: $_" for keys %{$self->{F}};
  0         0  
69              
70 0         0 return $self->{Sub}->( %{$self->{B}} );
  0         0  
71             }
72             # -----------------------------------------------------------------------------
73             sub Free
74             # -----------------------------------------------------------------------------
75             {
76 0     0 1 0 my $self = shift;
77              
78 0         0 return $self->{F};
79             }
80             # -----------------------------------------------------------------------------
81             sub Describe
82             # -----------------------------------------------------------------------------
83             {
84 1     1 0 86 my $self = shift;
85 1         2 my @s;
86              
87 1         2 while ( my ( $k, $v ) = each %{$self->{B}} ) { push @s, "$k => $v"; }
  1         8  
  0         0  
88 1         3 while ( my ( $k, $v ) = each %{$self->{F}} ) { push @s, "$k => ?" ; }
  1         5  
  0         0  
89              
90 1         7 return $self->{Sub} . ': ' . join ', ', @s;
91             }
92             # -----------------------------------------------------------------------------
93             sub NoMethod
94             # -----------------------------------------------------------------------------
95             {
96 0     0 0 0 my ( $Obj1, $Obj2, $Inv, $Op ) = @_;
97              
98 0 0 0     0 if ( defined $Obj2 || exists $Sub::DeferredPartial::Op::Binary::Ops{$Op} )
99             {
100 0 0       0 $Obj2 = Sub::DeferredPartial::Op::Nullary->new( $Obj2 ) unless ref $Obj2;
101 0 0       0 ( $Obj1, $Obj2 ) = ( $Obj2, $Obj1 ) if $Inv;
102 0         0 return Sub::DeferredPartial::Op::Binary->new( $Op, $Obj1, $Obj2 );
103             }
104 0         0 return Sub::DeferredPartial::Op::Unary->new( $Op, $Obj1 );
105             }
106             # -----------------------------------------------------------------------------
107             sub Defer
108             # -----------------------------------------------------------------------------
109             {
110 1     1 0 12 my $Sub = shift;
111              
112 1         12 return __PACKAGE__->new( $Sub, Sub::DeferredPartial::Attributes->Hash( $Sub ) );
113             }
114             # -----------------------------------------------------------------------------
115             1;
116              
117             =head1 NAME
118              
119             Sub::DeferredPartial - Deferred evaluation / partial application.
120              
121             =head1 SYNOPSIS
122              
123             use Sub::DeferredPartial 'def';
124              
125             $S = def sub : P1 P2 P3 { %_=@_; join '', @_{qw(P1 P2 P3)} };
126              
127             print $S->( P1 => 1, P2 => 2, P3 => 3 )->(); # 123
128              
129             $A = $S->( P3 => 1 ); # partial application
130             $B = $S->( P3 => 2 );
131              
132             $C = $A + $B; # deferred evaluation
133              
134             $D = $C->( P2 => 3 );
135             $E = $D->( P1 => 4 );
136              
137             print $E->(); # force evaluation: 863
138              
139             $F = $E - $D;
140              
141             $G = $F->( P1 => 0 ) / 2;
142              
143             print $G->(); # 400
144             print $G; # ( ( CODE(0x15e3818): P1 => 4, P2 => 3, P3 => 1 + CODE ...
145              
146             $F->(); # Error: Free parameter : P1
147             $A->( P3 => 7 ); # Error: Bound parameter: P3
148             $A->( P4 => 7 ); # Error: Wrong parameter: P4
149              
150             =head1 DESCRIPTION
151              
152             An instance of this class behaves like a sub (or, more precisely: subroutine
153             reference), but it supports partial application and the evaluation of
154             operators applied to such function objects is deferred too.
155             That means, evaluation has to be forced explicitly (which makes it easier to
156             add introspection capabilities).
157              
158             Objects that represent deferred (delayed, suspended) expressions are known
159             as suspensions or thunks in various programming circles.
160             Don't confuse with the same terms in the context of threads!
161              
162             When you use this module, you can specify the name of a subroutine:
163              
164             use Sub::DeferredPartial 'def';
165              
166             or accept the default C<'defer'>:
167              
168             use Sub::DeferredPartial;
169              
170             This subroutine will be imported into your current package and helps you to
171             create an instance of C:
172              
173             $S = defer sub : P1 P2 { "@_" };
174              
175             Please note that subroutine attributes are used to declare parameter names.
176             Now, C<$S> is an instance of C:
177              
178             print ref $S; # Sub::DeferredPartial
179              
180             and knows about the subroutine reference and its parameters:
181              
182             print $S; # CODE(0x15e3830): P1 => ?, P2 => ?
183              
184             Rudimentary introspection capabilities are available through stringification.
185             The question marks indicate that all parameters are free (unbound).
186              
187             Parameters are passed as flattened hash to emulate named parameters:
188              
189             $T = $S->( P1 => 1, P2 => 2 );
190              
191             This time, a new suspensions is created where all parameters are bound:
192              
193             print $T; # CODE(0x15e3830): P1 => 1, P2 => 2
194              
195             Although all parameters are bound, the evaluation of the function is deferred
196             and has to be forced explicitly:
197              
198             print $T->(); # P1 1 P2 2
199              
200             Up to this point, quite the same could be achieved with ordinary subroutines.
201             Indeed, every time we define a function (i.e. create an abstraction), the
202             evaluation of its body is deferred in some way.
203             However, every application would force the evaluation of the body.
204             And because Perl does not encourage currying, it would be tedious to write
205             a closure returning function every time we need to support partial
206             application.
207              
208             If you supply only some of the allowed arguments, a new suspension is
209             created with a mix of free and bound parameters:
210              
211             $A = $S->( P2 => 2 );
212              
213             Parameter P1 is still free, whereas P2 is bound:
214              
215             print $A; # CODE(0x15e3830): P2 => 2, P1 => ?
216              
217             If you merely need currying, you may consider modules like
218             L,
219             L or
220             L.
221              
222             However, this module goes further: The application of operators to
223             suspensions:
224              
225             $C = $A cmp $S->( P1 => 1 );
226              
227             creates yet another (kind of) suspension:
228              
229             print ref $C; # Sub::DeferredPartial::Op::Binary
230              
231             Depending on the operator - binary, unary or nullary (i.e. constants) -
232             different subclasses are used. But that shouldn’t bother you too much.
233             Assignment operators (mutators) are not supported.
234             Our poor man's reflection yields:
235              
236             print $C; # ( CODE(...): P2 => 2, P1 => ? cmp CODE(...): P1 => 1, P2 => ? )
237              
238             A suspended binary operator expects the union of the free parameters of
239             its operands:
240              
241             print map $C->( P1 => 1 )->( P2 => $_ )->(), 1..3; # 10-1
242              
243             The deferred evaluation strategy allows to write down expressions in
244             a natural way - without the need for a wrapper function.
245             This is the chief difference to the C<*::Curry> modules mentioned above.
246             Partial application aside, what comes closest is the
247             L
248             in the C module.
249              
250             =head1 DIAGNOSTICS
251              
252             =over 1
253              
254             =item Free parameter ...
255              
256             $A->(); # Free parameter: P1
257              
258             You cannot force evaluation until all parameters are bound.
259              
260             =item Bound parameter ...
261              
262             $A->( P2 => 7 ); # Bound parameter: P2
263              
264             You cannot bind a parameter that is already bound.
265              
266             =item Wrong parameter ...
267              
268             $A->( P3 => 7 ); # Wrong parameter: P3
269              
270             You cannot bind a parameter that was not declared.
271              
272             =back
273              
274             =head1 TODO
275              
276             =over
277            
278             =item Lazy evaluation
279              
280             Memoization is a common optimization strategy in this context.
281              
282             =item Conditional operator
283              
284             An I or I expression may be useful.
285              
286             =item Introspection capabilities
287              
288             Current introspection capabilities (stringification) are quite inflexible
289             and poking into the internals isn't state of the art ...
290              
291             =back
292              
293             =head1 ACKNOWLEDGMENT
294              
295             Many thanks to Gottlob Frege, Moses SchEnfinkel and Haskell Curry
296             for laying the groundwork.
297              
298             =head1 AUTHOR
299              
300             Steffen Goeldner
301              
302             =head1 COPYRIGHT
303              
304             Copyright (c) 2004 Steffen Goeldner. All rights reserved.
305              
306             This program is free software; you can redistribute it and/or
307             modify it under the same terms as Perl itself.
308              
309             =head1 SEE ALSO
310              
311             L, L, L, L, L.
312              
313             =cut