NAME
Sub::Compose
SYNOPSIS
use Sub::Compose qw( compose );
sub factory {
my ($mult) = @_;
return sub {
my $val = shift;
return $val * $mult;
}
}
my $times_two = factory( 2 );
my $times_three = factory( 3 );
my $times_six = compose( $times_two, $times_three );
$times_six->( 4 ); # 24
DESCRIPTION
The compose() function takes any number of subroutine references and creates a new subroutine reference from them that
Executes the code from the 1st with the parameters passed in
Executes the code from (N+1)th with the return value from the Nth.
Returns the return value from the last one.
RATIONALE
I like creating little subroutines that do interesting bits of work. I then like to build other subroutines by calling these guys and so on and so forth. Sometimes, I build up subroutines with calls 3 and 4 levels deep. This causes two problems:
Convoluted Stackdumps
Oftentimes, these little subroutines are validations that are supposed to die when they find something wrong. The stacktrace that confess() provides shows a whole bunch of subroutine calls that can be confusing.
This is demostrated in t/000_basic.t which highlights the difference between compose() and chain().
Performance
Calling a subroutine in Perl is one of the slowest single actions you can do. I have been finding myself with 20-30 extra subroutine calls just to program the way I want to program.
While I could pass little strings around and eventually eval them, I don't think that way - I think in terms of little pieces of work that I can put together like Tinkertoys(TM) or Legos(TM).
Also, strings don't close over variables. Most of the functions I use in this way are closures, so that method doesn't even work.
METHODOLOGY
Currently, this uses Data::Dump::Streamer to deparse the subroutines along with their lexical environments and then intelligently concatenates the output to form a single subroutine. As such, it has all issues that DDS has in terms of parsing coderefs. Please refer to that documentation for more details.
I am working on revamping this so that I manipulate the opcodes directly vs. deparsing. This should have increased performance and, hopefully, will reduce the likelihood of any edge cases. As this is my first foray into the world of perlguts, we'll see how it goes. :-)
FUNCTIONS
chain()
This is the old style of doing this and is provided for reference. It is implemented as so:
sub chain {
my (@subs) = @_;
return subname chainer => sub {
foreach my $sub ( @subs ) {
@_ = $sub->( @_ );
}
return @_;
};
}
As you can see, if there's a good 10-15 subroutines involved, this can be a lot of extra work, particularly if the subroutines are very small.
compose()
This is what this module is all about. Create a single subroutine from a bunch of subroutines that were passed in while still preserving closures.
BUGS
I'm sure this code has bugs somewhere. If you find one, please email me a failing test and I'll make it pass in the next release.
CODE COVERAGE
We use Devel::Cover to test the code coverage of our tests. Below is the Devel::Cover report on this module's test suite.
---------------------------- ------ ------ ------ ------ ------ ------ ------
File stmt bran cond sub pod time total
---------------------------- ------ ------ ------ ------ ------ ------ ------
blib/lib/Sub/Compose.pm 100.0 92.9 n/a 100.0 100.0 100.0 99.0
Total 100.0 92.9 n/a 100.0 100.0 100.0 99.0
---------------------------- ------ ------ ------ ------ ------ ------ ------
AUTHORS
Rob Kinyon <rob.kinyon@iinteractive.com>
Stevan Little <stevan.little@iinteractive.com>
Thanks to Infinity Interactive for generously donating our time.
COPYRIGHT AND LICENSE
Copyright 2005 by Infinity Interactive, Inc.
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.