Why not adopt me?
NAME
Closure::Loop - redo, last, next for closure based loops
VERSION
This document describes Closure::Loop version 0.0.3
SYNOPSIS
package MyIterator;
use Closure::Loop; # mixin
sub new {
my $class = shift;
return bless { }, $class;
}
sub forAll {
my $self = shift;
my $cb = pop || die "No callback";
for my $i (@_) {
eval {
$self->yield($cb, $i);
};
last if $self->is_last;
die $@ if $@;
}
}
package main;
my $iter = MyIterator->new();
my @in = ( 1, 2, 3 );
my @out = ( );
$iter->forAll(@in, sub {
my $i = shift;
$iter->next if $i == 2; # skip value
push @out, $i;
});
# @out is ( 1, 3 )
DESCRIPTION
An idea that Perl programmers can usefully borrow from Ruby is the concept of synthesizing new looping constructs by passing a block of code to a function that repeatedly calls it with successive values in a sequence.
In Ruby this looks like this:
def count_to_ten
i = 1
while i <= 10
yield i
i = i + 1
end
end
count_to_ten do |i|
puts i
end
In Perl the same thing looks like this:
sub count_to_ten {
my $block = shift;
my $i = 1;
while ($i <= 10) {
$block->($i);
$i++;
}
}
count_to_ten(sub {
my $i = shift;
print "$i\n";
});
That example is deliberately trivial. In practice this technique can be used to implement a loop like construct that, for example, walks the nodes in a binary tree - something that isn't easy with a normal loop.
The body of the loop is actually a closure so it has complete access to the lexical scope in which it is defined. In short it works just like the body of a loop for our purposes - with one exception.
For normal Perl loops it's possible to use the last, next and redo statements to modify iteration of the loop. That won't work as expected here. If we use them in our pseudo loop we'll get a warning (because we're actually trying to jump out of a subroutine using them) and they'll end up affecting the first enclosing loop or block they find - which may impact unpredictably on the iterator function, particularly if it doesn't actually contain a loop.
This module makes it easy to implement modules that expose iterator methods and which provide an interface for controlling the iteration of the loop which resembles an object oriented version of Perl's normal loop control statements.
Here's a simple module:
package MyIterator;
use Closure::Loop; # mixin
sub new {
my $class = shift;
return bless { }, $class;
}
sub forAll {
my $self = shift;
my $cb = pop || die "No callback";
for my $i (@_) {
eval {
$self->yield($cb, $i);
};
last if $self->is_last;
die $@ if $@;
}
}
The forAll iterator isn't very exciting; it just iterates over whatever arguments we pass to it. Still, it does enough to illustrate the point.
When you use Closure::Loop three loop control methods (redo
, last
and next
) and four helper methods (is_redo
, is_last
, is_next
and yield
) are added to your class.
Instead of calling the supplied block (or callback) directly your iterator should call yield
passing it the callback and any parameters. yield
handles redo
and next
itself. Calls to last
will throw an exception object which must be trapped with an eval and can be tested for by calling is_last
.
Because the logic for getting out of a (possibly deeply recursive) iterator depends on how it's structured your code needs to handle last
itself. The behaviour of next
and redo
can be handled automatically by yield
In the example above a call to the last
method is turned into a normal Perl last
to break out of the loop but it's up to you to handle it correctly depending on the semantics of the iterator.
Here's some code that calls the iterator:
use MyIterator;
my $iter = MyIterator->new();
my @in = ( 1, 2, 3 );
my @out = ( );
$iter->forAll(@in, sub {
my $i = shift;
$iter->next if $i == 2; # skip value
push @out, $i;
});
# @out is ( 1, 3 )
Where the next statement would be used in a normal loop we instead call the iterator object's next
method - but the effect is the same: the iterator immediately starts the next iteration. Similarly a call to the iterator's redo
method will restart the current iteration of the loop. Finally, because we implemented support for last
in forAll
we could call $iter->last to terminate iteration cleanly.
Optionally you may declare additional loop control methods like this
use Closure::Loop qw(prune);
That would import prune
and is_prune
into your module's namespace in addition to the standard loop control methods. You might then write a tree walker that would respond to
$iter->prune;
by aborting traversal of the current subtree without stopping altogether.
INTERFACE
next
-
Called within the body of a loop block causes the next iteration of the loop to start immediately.
redo
-
Restarts the current iteration of the loop passing the same parameters.
last
-
Immediately terminates iteration.
yield( args )
-
Called within the iterator to pass control to the loop body. Handles
redo
andnext
internally and throws an exception iflast
is called. The exception is a reference to a two element array:[ $self, 'last' ]
The
is_last
helper may be used to detect this specific exception value. Unrecognised exceptions should be rethrown:die $@ if $@;
is_last()
-
Returns true if the current value of $@ (or the value supplied as the first argument) is an exception that represents the
last
loop control message for this object. is_redo()
-
Returns true if the current value of $@ (or the value supplied as the first argument) is an exception that represents the
redo
loop control message for this object. is_next()
-
Returns true if the current value of $@ (or the value supplied as the first argument) is an exception that represents the
next
loop control message for this object.
DIAGNOSTICS
%s must be called as a method
-
All of the methods added to your class must actually be called as methods.
yield must be called with a callback (code ref)
-
yield
expects a code reference to call.
INCOMPATIBILITIES
None reported.
BUGS AND LIMITATIONS
No bugs have been reported.
Please report any bugs or feature requests to bug-closure-loop@rt.cpan.org
, or through the web interface at http://rt.cpan.org.
AUTHOR
Andy Armstrong <andy@hexten.net>
LICENCE AND COPYRIGHT
Copyright (c) 2006, Andy Armstrong <andy@hexten.net>
. All rights reserved.
This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See perlartistic.
DISCLAIMER OF WARRANTY
BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR, OR CORRECTION.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.