NAME
Functional::Utility - helper tools for light-weight functional programming.
SYNOPSIS
Slow down a piece of code, either by delaying $N seconds between runs or by taking $N times as long in between runs as a single run takes:
throttle { print scalar(localtime) . "\n" } delay => $N for 1..5;
throttle { do_something_expensive } factor => $N for 1..5;
Light-weight Moose-style before/after hooks for some arbitrary piece of code:
hook_run(
before => sub { warn "starting up...\n" },
run => $timed_block,
after => sub { warn "...all done!\n" },
);
Allow an anonymous function to be self-recursive without leaking memory:
y_combinator {
my ($recursive_function) = @_;
return sub {
# ... do work ...
$recursive_function->() if some_condition;
return;
};
};
DESCRIPTION
Functional::Utility provides a small collection of utilities to make certain pieces of functional programming a bit easier. Included are a few tools for controlling the behavior of existing functions.
EXPORTABLE FUNCTIONS
hook_run_hook PRE_CODEREF, CODEREF, POST_CODEREF
hook_run before => PRE_CODEREF, run => CODEREF, after => POST_CODEREF
Run PRE_CODEREF, then CODEREF, then POST_CODEREF. The return value of hook_run (and hook_run_hook) is the return value of CODEREF. CODEREF will be called in the same context as hook_run itself is called.
To write a light-weight timer function, you might do this:
sub timing_of (&) {
my $block_to_time = shift;
my $start;
return hook_run(
before => sub { $start = time },
run => $block_to_time,
after => sub { warn "code took " . (time - $start) . " seconds to run\n" },
);
}
And then if you had some pieces of code such as this:
my $line = <$fh>;
you could add timing around it by simply saying:
my $line = timing_of { <$fh> };
Since the return value of timing_of() is the return value of hook_run(), you could also write this, if you really cared to read an entire file into memory:
my @lines = <$fh>;
my @lines = timing_of { <$fh> };
Using hook_run(), you might write some more interesting functions beyond stopwatches:
sub nytprof_of (&;$) {
my $profiled_block = shift;
my $profile_output = shift || 'nytprof.out';
require Devel::NYTProf;
return hook_run(
before => sub { Devel::NYTProf->start_profiling },
run => $profiled_block,
after => sub { Devel::NYTProf->stop_profiling },
);
}
throttle BLOCK delay => N
Run BLOCK, sleeping N seconds between runs.
throttle BLOCK factor => N
Run BLOCK and time it, and then wait N times as long between runs as the code takes to run.
In order to prevent replication lag between our master/slave database setup, our DBAs request that we limit our inserts to 1,000 at a time and sleep 4 times as long between inserts as the inserts take to run.
I use
throttle { my @binds = @{shift @args}; $sth->execute(@binds) } factor => 4
while @args;
to manage the waiting; @args and $sth are built to accomodate 1,000 entries at a time.
y_combinator BLOCK
My short-sighted view of y_combinator is that it allows you to create a recursive anonymous subroutine in Perl without leaking memory.
Here's a naive recursive anonymous subroutine:
my $factorial;
$factorial = sub {
my $n = shift;
return $n if $n == 1;
return $n * $factorial->($n - 1);
};
my $factorial_6 = $factorial->(6);
If you're committed to using recursive anonymous subroutines in your design, and if you're on a version of perl lower than 5.16 (which introduces the __SUB__ token), then the y_combinator may be just what you need.
my $factorial = y_combinator {
my ($recurse) = @_;
return sub {
my $n = shift;
return $n if $n == 1;
return $n * $recurse->($n - 1);
};
};
my $factorial_6 = $factorial->(6);
For a much stronger treatment of the whats and whys of y_combinator(), view any of the excellent tutorials on this subject; I like http://mvanier.livejournal.com/2897.html
BUGS AND LIMITATIONS
The timing_of() function provided in the examples for hook_run() is a bit deceptive, because it ends up timing the overhead of hook_run() in addition to the piece of code being timed. However, if you're timing multiple pieces of code, the overhead of hook_run() will work out to be the same in all cases, at which point the deception is moot.
Currently, two pieces of code may not be throttled at the same time.
Please report any bugs or feature requests to this project's Github page: http://github.com/belden/perl-functional-utility/issues.
A NOTE ON CONTRIBUTING
This is a growing collection. You may contribute your own functional utilities via this project's Github page: http://github.com/belden/perl-functional-utility/issues.
AUTHOR
(c) 2013 Belden Lyman <belden@cpan.org>
LICENSE
You may use and redistribute this software under the same terms as Perl itself.