NAME

Sub::DeferredPartial - Deferred evaluation / partial application.

SYNOPSIS

use Sub::DeferredPartial 'def';

$S = def sub : P1 P2 P3 { %_=@_; join '', @_{qw(P1 P2 P3)} };

print $S->( P1 => 1, P2 => 2, P3 => 3 )->(); # 123

$A = $S->( P3 => 1 );  # partial application
$B = $S->( P3 => 2 );

$C = $A + $B;    # deferred evaluation

$D = $C->( P2 => 3 );
$E = $D->( P1 => 4 );

print $E->();    # force evaluation: 863

$F = $E - $D;

$G = $F->( P1 => 0 ) / 2;

print $G->();    # 400
print $G;        # ( ( CODE(0x15e3818): P1 => 4, P2 => 3, P3 => 1 + CODE ...

$F->();          # Error: Free parameter : P1
$A->( P3 => 7 ); # Error: Bound parameter: P3
$A->( P4 => 7 ); # Error: Wrong parameter: P4

DESCRIPTION

An instance of this class behaves like a sub (or, more precisely: subroutine reference), but it supports partial application and the evaluation of operators applied to such function objects is deferred too. That means, evaluation has to be forced explicitly (which makes it easier to add introspection capabilities).

Objects that represent deferred (delayed, suspended) expressions are known as suspensions or thunks in various programming circles. Don't confuse with the same terms in the context of threads!

When you use this module, you can specify the name of a subroutine:

use Sub::DeferredPartial 'def';

or accept the default 'defer':

use Sub::DeferredPartial;

This subroutine will be imported into your current package and helps you to create an instance of Sub::DeferredPartial:

$S = defer sub : P1 P2 { "@_" };

Please note that subroutine attributes are used to declare parameter names. Now, $S is an instance of Sub::DeferredPartial:

print ref $S;  # Sub::DeferredPartial

and knows about the subroutine reference and its parameters:

print $S;  # CODE(0x15e3830): P1 => ?, P2 => ?

Rudimentary introspection capabilities are available through stringification. The question marks indicate that all parameters are free (unbound).

Parameters are passed as flattened hash to emulate named parameters:

$T = $S->( P1 => 1, P2 => 2 );

This time, a new suspensions is created where all parameters are bound:

print $T;  # CODE(0x15e3830): P1 => 1, P2 => 2

Although all parameters are bound, the evaluation of the function is deferred and has to be forced explicitly:

print $T->();  # P1 1 P2 2

Up to this point, quite the same could be achieved with ordinary subroutines. Indeed, every time we define a function (i.e. create an abstraction), the evaluation of its body is deferred in some way. However, every application would force the evaluation of the body. And because Perl does not encourage currying, it would be tedious to write a closure returning function every time we need to support partial application.

If you supply only some of the allowed arguments, a new suspension is created with a mix of free and bound parameters:

$A = $S->( P2 => 2 );

Parameter P1 is still free, whereas P2 is bound:

print $A;  # CODE(0x15e3830): P2 => 2, P1 => ?

If you merely need currying, you may consider modules like Sub::Curry, Attribute::Curried or Perl6::Currying.

However, this module goes further: The application of operators to suspensions:

$C = $A cmp $S->( P1 => 1 );

creates yet another (kind of) suspension:

print ref $C;  # Sub::DeferredPartial::Op::Binary

Depending on the operator - binary, unary or nullary (i.e. constants) - different subclasses are used. But that shouldn’t bother you too much. Assignment operators (mutators) are not supported. Our poor man's reflection yields:

print $C;  # ( CODE(...): P2 => 2, P1 => ? cmp CODE(...): P1 => 1, P2 => ? )

A suspended binary operator expects the union of the free parameters of its operands:

print map $C->( P1 => 1 )->( P2 => $_ )->(), 1..3;  # 10-1

The deferred evaluation strategy allows to write down expressions in a natural way - without the need for a wrapper function. This is the chief difference to the *::Curry modules mentioned above. Partial application aside, what comes closest is the Symbolic calculator example in the overload module.

DIAGNOSTICS

Free parameter ...
$A->();          # Free parameter: P1

You cannot force evaluation until all parameters are bound.

Bound parameter ...
$A->( P2 => 7 ); # Bound parameter: P2

You cannot bind a parameter that is already bound.

Wrong parameter ...
$A->( P3 => 7 ); # Wrong parameter: P3

You cannot bind a parameter that was not declared.

TODO

Lazy evaluation

Memoization is a common optimization strategy in this context.

Conditional operator

An if-then-else or case expression may be useful.

Introspection capabilities

Current introspection capabilities (stringification) are quite inflexible and poking into the internals isn't state of the art ...

ACKNOWLEDGMENT

Many thanks to Gottlob Frege, Moses Schönfinkel and Haskell Curry for laying the groundwork.

AUTHOR

Steffen Goeldner <sgoeldner@cpan.org>

COPYRIGHT

Copyright (c) 2004 Steffen Goeldner. All rights reserved.

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

SEE ALSO

perl, Sub::Curry, Attribute::Curried, Perl6::Currying, overload.

1 POD Error

The following errors were encountered while parsing the POD:

Around line 232:

Non-ASCII character seen before =encoding in 'shouldn’t'. Assuming CP1252