Iterator-0.03/0000755000175000017500000000000012556541100011264 5ustar abeabeIterator-0.03/Makefile.PL0000644000175000017500000000321510322363764013246 0ustar abeabeuse ExtUtils::MakeMaker; # See lib/ExtUtils/MakeMaker.pm for details of how to influence # the contents of the Makefile that is written. WriteMakefile( 'NAME' => 'Iterator', 'VERSION_FROM' => 'Iterator.pm', # finds $VERSION 'PREREQ_PM' => {'Test::Simple' => '0.40', 'Exception::Class' => 1.21, }, ($] >= 5.005 ? ## Add these new keywords supported since 5.005 (ABSTRACT_FROM => 'Iterator.pm', # retrieve abstract from module AUTHOR => 'Eric Roode ') : ()), ); package MY; sub dist_core { my $text = shift->SUPER::dist_core(@_); $text =~ s/^(\$\(DISTVNAME\)[^:]*): (.*)/$1: ppd ppm $2/mg; return $text; } sub realclean { my $text = shift->SUPER::realclean(@_); $text .= <<'CLEAN'; rm -rf $(PPDFILE) $(PPMFILE) CLEAN return $text; } sub ppd { my $self = shift; my $text = $self->SUPER::ppd(@_); $text =~ s/(ppd\s*:)/$1 \$(PPDFILE)\n\n\$(PPDFILE) :/; $text =~ s[(?<={DISTNAME}-$self->{VERSION}/$self->{DISTNAME}-ppm.tar.gz]; # This release is allegedly OS and architecture independent (as it's pure perl) $text =~ s/]+>(?:\\[nt])*//; $text =~ s/]+>(?:\\[nt])*//; $text = <<'PRE' . $text; PPMNAME = $(DISTNAME)-ppm PPDFILE = $(DISTNAME).ppd PPMFILE = $(PPMNAME).tar.gz PRE $text .= <<'PPM'; ppm: $(PPMFILE) $(PPMFILE): pm_to_blib $(INST_LIBDIR)/.exists $(INST_ARCHAUTODIR)/.exists $(INST_AUTODIR)/.exists $(TAR) $(TARFLAGS) - blib | $(COMPRESS) -c > $(PPMFILE) PPM return $text; } Iterator-0.03/META.yml0000644000175000017500000000057010322535154012540 0ustar abeabe# http://module-build.sourceforge.net/META-spec.html #XXXXXXX This is a prototype!!! It will change in the future!!! XXXXX# name: Iterator version: 0.03 version_from: Iterator.pm installdirs: site requires: Exception::Class: 1.21 Test::Simple: 0.40 distribution_type: module generated_by: ExtUtils::MakeMaker version 6.17 Iterator-0.03/t/0000755000175000017500000000000010322535155011531 5ustar abeabeIterator-0.03/t/doc.t0000644000175000017500000000420210322363764012466 0ustar abeabeuse strict; use Test::More tests => 13; use Iterator; # Check that the documentation examples work. sub begins_with { my ($actual, $expected, $test_name) = @_; $actual = substr($actual, 0, length $expected); @_ = ($actual, $expected, $test_name); goto &is; } my ($iter, $it, $x, @vals); # from the README (13) sub upto { my ($m, $n) = @_; return Iterator->new( sub { return $m++ if $m <= $n; Iterator::X::Am_Now_Exhausted->throw(); }); } @vals = (); eval { $it = upto (3, 5); }; is ($@, q{}, q{README iterator created, no exception}); eval { push @vals, $it->value; # returns 3 push @vals, $it->value; # returns 4 push @vals, $it->value; # returns 5 }; is ($@, q{}, q{README iterator; first three okay}); is_deeply (\@vals, [3, 4, 5], q{README iterator: expected values ok}); eval { my $i = $it->value; # throws an Iterator::X::Exhausted exception. }; isnt ($@, q{}, q{README iterator: exception thrown}); ok (Iterator::X->caught(), q{README exception: correct base type}); ok (Iterator::X::Exhausted->caught(), q{README exception: correct specific type}); begins_with ($@, q{Iterator is exhausted}, q{README iterator exception formatted propertly.}); { my $another_it; @vals = (); eval { $another_it = upto (7, 10); while ($another_it->isnt_exhausted) { push @vals, $another_it->value; } # The above [pushes] 7, 8, 9, 10 and throws no exceptions. }; is ($@, q{}, q{$another_it: no exception thrown}); is_deeply (\@vals, [7, 8, 9, 10], q{$another_it: expected values}); eval { # Another call to $another_it->value would throw an exception. $another_it->value }; isnt ($@, q{}, q{$another_it iterator: exception thrown}); ok (Iterator::X->caught(), q{$another_it exception: correct base type}); ok (Iterator::X::Exhausted->caught(), q{$another_it exception: correct specific type}); begins_with ($@, q{Iterator is exhausted}, q{$another_it iterator exception formatted propertly.}); } Iterator-0.03/t/value.t0000644000175000017500000000665510322363764013053 0ustar abeabeuse strict; use Test::More tests => 33; use Iterator; # Check that value() works. # Also tests is_ and isnt_exhausted. sub begins_with { my ($actual, $expected, $test_name) = @_; $actual = substr($actual, 0, length $expected); @_ = ($actual, $expected, $test_name); goto &is; } my ($iter, $x, $val, $exh, $nex); # Create iterator for us to work with (1) eval { my $i = 1; my $max = 3; $iter = Iterator->new ( sub { Iterator::X::Am_Now_Exhausted->throw() if ($i > $max); return $i++; } ); }; is $@, q{}, q{Created simple iterator; no exception}; # That iterator should not be exhausted already. (3) eval { $exh = $iter->is_exhausted; $nex = $iter->isnt_exhausted; }; is ($@, q{}, q{Exhausted check didn't barf.}); ok (!$exh, q{Not exhausted yet.}); ok ( $nex, q{Not exhausted yet.}); # Fetch a value (2) eval { $val = $iter->value(); }; is $@, q{}, q{Pulled first value from iterator; no exception}; cmp_ok ($val, '==', 1, q{First value is correct}); # That iterator should not be exhausted yet. (3) eval { $exh = $iter->is_exhausted; $nex = $iter->isnt_exhausted; }; is ($@, q{}, q{Exhausted check didn't barf.}); ok (!$exh, q{Not exhausted yet.}); ok ( $nex, q{Not exhausted yet.}); # Fetch a value (2) eval { $val = $iter->value(); }; is $@, q{}, q{Pulled second value from iterator; no exception}; cmp_ok ($val, '==', 2, q{Second value is correct}); # That iterator should not be exhausted yet. (3) eval { $exh = $iter->is_exhausted; $nex = $iter->isnt_exhausted; }; is ($@, q{}, q{Exhausted check didn't barf.}); ok (!$exh, q{Not exhausted yet.}); ok ( $nex, q{Not exhausted yet.}); # Fetch a value (2) eval { $val = $iter->value(); }; is $@, q{}, q{Pulled third value from iterator; no exception}; cmp_ok ($val, '==', 3, q{Third value is correct}); # Iterator should now be exhausted. (3) eval { $exh = $iter->is_exhausted; $nex = $iter->isnt_exhausted; }; is ($@, q{}, q{Exhausted check didn't barf.}); ok ( $exh, q{Now exhausted.}); ok (!$nex, q{Now exhausted.}); # Attempt to fetch a value from it (4) eval { $val = $iter->value(); }; $x = $@; isnt $@, q{}, q{Pulled fourth value from iterator; got exception}; ok (Iterator::X->caught(), q{Exhausted exception base class ok}); ok (Iterator::X::Exhausted->caught(), q{Exhausted exception specific class ok}); begins_with $x, q{Iterator is exhausted}, q{Exhausted exception works as a string, too}; # Should still be able to check exhausted state. (3) eval { $exh = $iter->is_exhausted; $nex = $iter->isnt_exhausted; }; is ($@, q{}, q{Exhausted check didn't barf.}); ok ( $exh, q{Now exhausted.}); ok (!$nex, q{Now exhausted.}); # Test user exception. (7) eval { my $internal = 1; $iter = Iterator->new(sub { die "what the heck?" if $internal > 2; return $internal++; }); }; is ($@, q{}, q{User-error iterator created fine.}); eval { $val = $iter->value; }; is ($@, q{}, q{User-error iterator; first value no error.}); cmp_ok ($val, '==', 1, q{User-error iterator; first value correct}); eval { $val = $iter->value; }; isnt ($@, q{}, q{User-error iterator blew up on time}); $x = $@; ok (Iterator::X->caught(), q{User-error base exception caught}); ok (Iterator::X::User_Code_Error->caught(), q{User-error specific exception caught}); begins_with ($x, "what the heck?", q{User-error; proper string value.}); Iterator-0.03/t/new.t0000644000175000017500000000461010322363764012515 0ustar abeabeuse strict; use Test::More tests => 18; use Iterator; # Check that new() fails when it should. sub begins_with { my ($actual, $expected, $test_name) = @_; $actual = substr($actual, 0, length $expected); @_ = ($actual, $expected, $test_name); goto &is; } my ($iter, $x); # New: too few (4) eval { $iter = Iterator->new(); }; $x = $@; isnt $x, q{}, q{Too few parameters to new -> exception thrown}; ok (Iterator::X->caught(), q{Too-few exception base class ok}); ok (Iterator::X::Parameter_Error->caught(), q{Too-few exception specific class ok}); begins_with $x, q{Too few parameters to Iterator->new()}, q{Too-few exception works as a string, too}; # New: too many (4) eval { $iter = Iterator->new(sub {die}, 'whoa there'); }; $x = $@; isnt $x, q{}, q{Too many parameters to new -> exception thrown}; ok (Iterator::X->caught(), q{Too-many exception base class ok}); ok (Iterator::X::Parameter_Error->caught(), q{Too-many exception specific class ok}); begins_with $x, q{Too many parameters to Iterator->new()}, q{Too-many exception works as a string, too}; # New: wrong type (4) eval { $iter = Iterator->new('whoa there'); }; $x = $@; isnt $x, q{}, q{Wrong type of parameter to new -> exception thrown}; ok (Iterator::X->caught(), q{Wrong-type exception base class ok}); ok (Iterator::X::Parameter_Error->caught(), q{Wrong-type exception specific class ok}); begins_with $x, q{Parameter to Iterator->new() must be code reference}, q{Wrong-type exception works as a string, too}; # New: wrong type (looks like code but isn't) (4) eval { $iter = Iterator->new({qw/whoa there/}); }; $x = $@; isnt $x, q{}, q{Bad code ref parameter to new -> exception thrown}; ok (Iterator::X->caught(), q{Bad-coderef exception base class ok}); ok (Iterator::X::Parameter_Error->caught(), q{Bad-coderef exception specific class ok}); begins_with $x, q{Parameter to Iterator->new() must be code reference}, q{Bad-coderef exception works as a string, too}; # New: everything fine (1) eval { my $i = 0; $iter = Iterator->new( sub {return $i++}); }; $x = $@; is $x, q{}, q{Simple invocation: no exception}; # New: everything fine (1) eval { my $i = 0; $iter = Iterator->new( sub { Iterator::X::Am_Now_Exhausted->throw if $i > 10; return $i++; }); }; $x = $@; is $x, q{}, q{more-complicated invocation: no exception}; Iterator-0.03/Iterator.pm0000644000175000017500000006120710322363764013430 0ustar abeabe=for gpg -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 =head1 NAME Iterator - A general-purpose iterator class. =head1 VERSION This documentation describes version 0.03 of Iterator.pm, October 10, 2005. =cut use strict; use warnings; package Iterator; our $VERSION = '0.03'; # Declare exception classes use Exception::Class ( 'Iterator::X' => { description => 'Generic Iterator exception', }, 'Iterator::X::Parameter_Error' => { isa => 'Iterator::X', description => 'Iterator method parameter error', }, 'Iterator::X::OptionError' => { isa => 'Iterator::X', fields => 'name', description => 'A bad option was passed to an iterator method or function', }, 'Iterator::X::Exhausted' => { isa => 'Iterator::X', description => 'Attempt to next_value () on an exhausted iterator', }, 'Iterator::X::Am_Now_Exhausted' => { isa => 'Iterator::X', description => 'Signals Iterator object that it is now exhausted', }, 'Iterator::X::User_Code_Error' => { isa => 'Iterator::X', fields => 'eval_error', description => q{An exception was thrown within the user's code}, }, 'Iterator::X::IO_Error' => { isa => 'Iterator::X', fields => 'os_error', description => q{An I/O error occurred}, }, 'Iterator::X::Internal_Error' => { isa => 'Iterator::X', description => 'An Iterator.pm internal error. Please contact author.', }, ); # Class method to help caller catch exceptions BEGIN { # Dave Rolsky added this subroutine in v1.22 of Exception::Class. # Thanks, Dave! # We define it here so we have the functionality in pre-1.22 versions; # we make it conditional so as to avoid a warning in post-1.22 versions. *Exception::Class::Base::caught = sub { my $class = shift; return Exception::Class->caught($class); } if $Exception::Class::VERSION lt '1.22'; } # Croak-like location of error sub Iterator::X::location { my ($pkg,$file,$line); my $caller_level = 0; while (1) { ($pkg,$file,$line) = caller($caller_level++); last if $pkg !~ /\A Iterator/x && $pkg !~ /\A Exception::Class/x } return "at $file line $line"; } # Die-like location of error sub Iterator::X::Internal_Error::location { my $self = shift; return "at " . $self->file () . " line " . $self->line () } # Override full_message, to report location of error in caller's code. sub Iterator::X::full_message { my $self = shift; my $msg = $self->message; return $msg if substr($msg,-1,1) eq "\n"; $msg =~ s/[ \t]+\z//; # remove any trailing spaces (is this necessary?) return $msg . q{ } . $self->location () . qq{\n}; } ## Constructor # Method name: new # Synopsis: $iterator = Iterator->new( $code_ref ); # Description: Object constructor. # Created: 07/27/2005 by EJR # Parameters: $code_ref - the iterator sequence generation code. # Returns: New Iterator. # Exceptions: Iterator::X::Parameter_Error (via _initialize) sub new { my $class = shift; my $self = \do {my $anonymous}; bless $self, $class; $self->_initialize(@_); return $self; } { # encapsulation enclosure # Attributes: my %code_for; # The sequence code (coderef) for each object. my %is_exhausted; # Boolean: is this object exhausted? my %next_value_for; # One-item lookahead buffer for each object. # [if you update this list of attributes, be sure to edit DESTROY] # Method name: _initialize # Synopsis: $iterator->_initialize( $code_ref ); # Description: Object initializer. # Created: 07/27/2005 by EJR # Parameters: $code_ref - the iterator sequence generation code. # Returns: Nothing. # Exceptions: Iterator::X::Parameter_Error # Iterator::X::User_Code_Error # Notes: For internal module use only. # Caches the first value of the iterator in %next_value_for. sub _initialize { my $self = shift; Iterator::X::Parameter_Error->throw(q{Too few parameters to Iterator->new()}) if @_ < 1; Iterator::X::Parameter_Error->throw(q{Too many parameters to Iterator->new()}) if @_ > 1; my $code = shift; Iterator::X::Parameter_Error->throw (q{Parameter to Iterator->new() must be code reference}) if ref $code ne 'CODE'; $code_for {$self} = $code; # Get the next (first) value for this iterator eval { $next_value_for{$self} = $code-> (); }; my $ex; if ($ex = Iterator::X::Am_Now_Exhausted->caught ()) { # Starting off exhausted is okay $is_exhausted{$self} = 1; } elsif ($@) { Iterator::X::User_Code_Error->throw (message => "$@", eval_error => $@); } return; } # Method name: DESTROY # Synopsis: (none) # Description: Object destructor. # Created: 07/27/2005 by EJR # Parameters: None. # Returns: Nothing. # Exceptions: None. # Notes: Invoked automatically by perl. # Releases the hash entries used by the object. # Module would leak memory otherwise. sub DESTROY { my $self = shift; delete $code_for{$self}; delete $is_exhausted{$self}; delete $next_value_for{$self}; } # Method name: value # Synopsis: $next_value = $iterator->value(); # Description: Returns each value of the sequence in turn. # Created: 07/27/2005 by EJR # Parameters: None. # Returns: Next value, as generated by caller's code ref. # Exceptions: Iterator::X::Exhausted # Notes: Keeps one forward-looking value for the iterator in # %next_value_for. This is so we have something to # return when user's code throws Am_Now_Exhausted. sub value { my $self = shift; Iterator::X::Exhausted->throw(q{Iterator is exhausted}) if $is_exhausted{$self}; # The value that we'll be returning this time. my $this_value = $next_value_for{$self}; # Compute the value that we'll return next time eval { $next_value_for{$self} = $code_for{$self}->(@_); }; if (my $ex = Iterator::X::Am_Now_Exhausted->caught ()) { # Aha, we're done; we'll have to stop next time. $is_exhausted{$self} = 1; } elsif ($@) { Iterator::X::User_Code_Error->throw (message => "$@", eval_error => $@); } return $this_value; } # Method name: is_exhausted # Synopsis: $boolean = $iterator->is_exhausted(); # Description: Flag indicating that the iterator is exhausted. # Created: 07/27/2005 by EJR # Parameters: None. # Returns: Current value of %is_exhausted for this object. # Exceptions: None. sub is_exhausted { my $self = shift; return $is_exhausted{$self}; } # Method name: isnt_exhausted # Synopsis: $boolean = $iterator->isnt_exhausted(); # Description: Flag indicating that the iterator is NOT exhausted. # Created: 07/27/2005 by EJR # Parameters: None. # Returns: Logical NOT of %is_exhausted for this object. # Exceptions: None. sub isnt_exhausted { my $self = shift; return ! $is_exhausted{$self}; } } # end of encapsulation enclosure # Function name: is_done # Synopsis: Iterator::is_done (); # Description: Convenience function. Throws an Am_Now_Exhausted exception. # Created: 08/02/2005 by EJR, per Will Coleda's suggestion. # Parameters: None. # Returns: Doesn't return. # Exceptions: Iterator::X::Am_Now_Exhausted sub is_done { Iterator::X::Am_Now_Exhausted->throw() } 1; __END__ =head1 SYNOPSIS use Iterator; # Making your own iterators from scratch: $iterator = Iterator->new ( sub { code } ); # Accessing an iterator's values in turn: $next_value = $iterator->value(); # Is the iterator out of values? $boolean = $iterator->is_exhausted(); $boolean = $iterator->isnt_exhausted(); # Within {code}, above: Iterator::is_done(); # to signal end of sequence. =head1 DESCRIPTION This module is meant to be the definitive implementation of iterators, as popularized by Mark Jason Dominus's lectures and recent book (I, Morgan Kauffman, 2005). An "iterator" is an object, represented as a code block that generates the "next value" of a sequence, and generally implemented as a closure. When you need a value to operate on, you pull it from the iterator. If it depends on other iterators, it pulls values from them when it needs to. Iterators can be chained together (see L for functions that help you do just that), queueing up work to be done but I until a value is needed at the front end of the chain. At that time, one data value is pulled through the chain. Contrast this with ordinary array processing, where you load or compute all of the input values at once, then loop over them in memory. It's analogous to the difference between looping over a file one line at a time, and reading the entire file into an array of lines before operating on it. Iterator.pm provides a class that simplifies creation and use of these iterator objects. Other C modules (see L) provide many general-purpose and special-purpose iterator functions. Some iterators are infinite (that is, they generate infinite sequences), and some are finite. When the end of a finite sequence is reached, the iterator code block should throw an exception of the type C; this is usually done via the L function.. This will signal the Iterator class to mark the object as exhausted. The L method will then return true, and the L method will return false. Any further calls to the L method will throw an exception of the type C. See L. Note that in many, many cases, you will not need to explicitly create an iterator; there are plenty of iterator generation and manipulation functions in the other associated modules. You can just plug them together like building blocks. =head1 METHODS =over 4 =item new $iter = Iterator->new( sub { code } ); Creates a new iterator object. The code block that you provide will be invoked by the L method. The code block should have some way of maintaining state, so that it knows how to return the next value of the sequence each time it is called. If the code is called after it has generated the last value in its sequence, it should throw an exception: Iterator::X::Am_Now_Exhausted->throw (); This very commonly needs to be done, so there is a convenience function for it: Iterator::is_done (); =item value $next_value = $iter->value (); Returns the next value in the iterator's sequence. If C is called on an exhausted iterator, an C exception is thrown. Note that these iterators can only return scalar values. If you need your iterator to return a list or hash, it will have to return an arrayref or hashref. =item is_exhausted $bool = $iter->is_exhausted (); Returns true if the iterator is exhausted. In this state, any call to the iterator's L method will throw an exception. =item isnt_exhausted $bool = $iter->isnt_exhausted (); Returns true if the iterator is not yet exhausted. =back =head1 FUNCTION =over 4 =item is_done Iterator::is_done(); You call this function after your iterator code has generated its last value. See L. This is simply a convenience wrapper for Iterator::X::Am_Now_Exhausted->throw(); =back =head1 THINKING IN ITERATORS Typically, when people approach a problem that involves manipulating a bunch of data, their first thought is to load it all into memory, into an array, and work with it in-place. If you're only dealing with one element at a time, this approach usually wastes memory needlessly. For example, one might get a list of files to operate on, and loop over it: my @files = fetch_file_list(....); foreach my $file (@files) ... If C were modified to return an iterator instead of an array, the same code could look like this: my $file_iterator = fetch_file_list(...) while ($file_iterator->isnt_exhausted) ... The advantage here is that the whole list does not take up memory while each individual element is being worked on. For a list of files, that's probably not a lot of overhead. For the contents of a file, on the other hand, it could be huge. If a function requires a list of items as its input, the overhead is tripled: sub myfunc { my @things = @_; ... Now in addition to the array in the calling code, Perl must copy that array to C<@_>, and then copy it again to C<@things>. If you need to massage the input from somewhere, it gets even worse: my @data = get_things_from_somewhere(); my @filtered_data = grep {code} @data; my @transformed_data = map {code} @filtered_data; myfunc (@transformed_data); If C is rewritten to use an Iterator instead of an array, things become much simpler: my $data = ilist (get_things_from_somewhere()); $filtered_data = igrep {code} $data; $transformed_data = imap {code} $filtered_data; myfunc ($transformed_data); (This example assumes that the C function cannot be modified to return an Iterator. If it can, so much the better!) Now the original list is still in memory, inside the C<$data> Iterator, but everwhere else, there is only one data element in memory at a time. Another advantage of Iterators is that they're homogeneous. This is useful for uncoupling library code from application code. Suppose you have a library function that grabs data from a filehandle: sub my_lib_func { my $fh = shift; ... If you need C to get its data from a different source, you must either modify it, or make a new copy of it that gets its input differently, or you must jump through hoops to make the new input stream look like a Perl filehandle. On the other hand, if C accepts an iterator, then you can pass it data from a filehandle: my $data = ifile "my_input.txt"; $result = my_lib_func($data); Or a database handle: my $data = imap {$_->{IMPORTANT_COLUMN}} idb_rows($dbh, 'select IMPORTANT_COLUMN from foo'); $result = my_lib_func($data); If you later decide you need to transform the data, or process only every 10th data row, or whatever: $result = my_lib_func(imap {magic($_)} $data); $result = my_lib_func(inth 10, $data); The library function doesn't care. All it needs is an iterator. Chapter 4 of Dominus's book (See L) covers this topic in some detail. =head2 Word of Warning When you use an iterator in separate parts of your program, or as an argument to the various iterator functions, you do I get a copy of the iterator's stream of values. In other words, if you grab a value from an iterator, then some other part of the program grabs a value from the same iterator, you will be getting different values. This can be confusing if you're not expecting it. For example: my $it_one = Iterator->new ({something}); my $it_two = some_iterator_transformation $it_one; my $value = $it_two->value(); my $whoops = $it_one->value; Here, C takes an iterator as an argument, and returns an iterator as a result. When a value is fetched from C<$it_two>, it internally grabs a value from C<$it_one> (and presumably transforms it somehow). If you then grab a value from C<$it_one>, you'll get its I value (or third, or whatever, depending on how many values C<$it_two> grabbed), not the first. =head1 TUTORIAL Let's create a date iterator. It'll take a L object as a starting date, and return successive days -- that is, it'll add 1 day each iteration. It would be used as follows: use DateTime; $iter = (...something...); $day1 = $iter->value; # Initial date $day2 = $iter->value; # One day later $day3 = $iter->value; # Two days later The easiest way to create such an iterator is by using a I. If you're not familiar with the concept, it's fairly simple: In Perl, the code within an I has access to all the I that were in scope at the time the block was created. After the program then leaves that lexical scope, those lexical variables remain accessible by that code block for as long as it exists. This makes it very easy to create iterators that maintain their own state. Here we'll create a lexical scope by using a pair of braces: my $iter; { my $dt = DateTime->now(); $iter = Iterator->new( sub { my $return_value = $dt->clone; $dt->add(days => 1); return $return_value; }); } Because C<$dt> is lexically scoped to the outermost block, it is not addressable from any code elsewhere in the program. But the anonymous block within the L method's parentheses I see C<$dt>. So C<$dt> does not get garbage-collected as long as C<$iter> contains a reference to it. The code within the anonymous block is simple. A copy of the current C<$dt> is made, one day is added to C<$dt>, and the copy is returned. You'll probably want to encapsulate the above block in a subroutine, so that you could call it from anywhere in your program: sub date_iterator { my $dt = DateTime->now(); return Iterator->new( sub { my $return_value = $dt->clone; $dt->add(days => 1); return $return_value; }); } If you look at the source code in L, you'll see that just about all of the functions that create iterators look very similar to the above C function. Of course, you'd probably want to be able to pass arguments to C, say a starting date, maybe an increment other than "1 day". But the basic idea is the same. The above date iterator is an infinite (well, unbounded) iterator. Let's look at how to indicate that your iterator has reached the end of its sequence of values. Let's write a scaled-down version of L from the Iterator::Util module -- one that takes a start value and an end value and always increments by 1. sub irange_limited { my ($start, $end) = @_; return Iterator->new (sub { Iterator::is_done if $start > $end; return $start++; }); } The iterator itself is very simple (this sort of thing gets to be easy once you get the hang of it). The new element here is the signalling that the sequence has ended, and the iterator's work is done. L is how your code indicates this to the Iterator object. You may also want to throw an exception if the user specified bad input parameters. There are a couple ways you can do this. ... die "Too few parameters to irange_limited" if @_ < 2; die "Too many parameters to irange_limited" if @_ > 2; my ($start, $end) = @_; ... This is the simplest way; you just use C (or C). You may choose to throw an Iterator parameter error, though; this will make your function work more like one of Iterator.pm's built in functions: ... Iterator::X::Parameter_Error->throw( "Too few parameters to irange_limited") if @_ < 2; Iterator::X::Parameter_Error->throw( "Too many parameters to irange_limited") if @_ > 2; my ($start, $end) = @_; ... =head1 EXPORTS No symbols are exported to the caller's namespace. =head1 DIAGNOSTICS Iterator uses L objects for throwing exceptions. If you're not familiar with Exception::Class, don't worry; these exception objects work just like C<$@> does with C and C, but they are easier to work with if you are trapping errors. All exceptions thrown by Iterator have a base class of Iterator::X. You can trap errors with an eval block: eval { $foo = $iterator->value(); }; and then check for errors as follows: if (Iterator::X->caught()) {... You can look for more specific errors by looking at a more specific class: if (Iterator::X::Exhausted->caught()) {... Some exceptions may provide further information, which may be useful for your exception handling: if (my $ex = Iterator::X::User_Code_Error->caught()) { my $exception = $ex->eval_error(); ... If you choose not to (or cannot) handle a particular type of exception (for example, there's not much to be done about a parameter error), you should rethrow the error: if (my $ex = Iterator::X->caught()) { if ($ex->isa('Iterator::X::Something_Useful')) { ... } else { $ex->rethrow(); } } =over 4 =item * Parameter Errors Class: C You called an Iterator method with one or more bad parameters. Since this is almost certainly a coding error, there is probably not much use in handling this sort of exception. As a string, this exception provides a human-readable message about what the problem was. =item * Exhausted Iterators Class: C You called L on an iterator that is exhausted; that is, there are no more values in the sequence to return. As a string, this exception is "Iterator is exhausted." =item * End of Sequence Class: C This exception is not thrown directly by any Iterator.pm methods, but is to be thrown by iterator sequence generation code; that is, the code that you pass to the L constructor. Your code won't catch an C exception, because the Iterator object will catch it internally and set its L flag. The simplest way to throw this exception is to use the L function: Iterator::is_done() if $something; =item * User Code Exceptions Class: C This exception is thrown when the sequence generation code throws any sort of error besides C. This could be because your code explicitly threw an error (that is, Cd), or because it otherwise encountered an exception (any runtime error). This exception has one method, C, which returns the original C<$@> that was trapped by the Iterator object. This may be a string or an object, depending on how C was invoked. As a string, this exception evaluates to the stringified C<$@>. =item * I/O Errors Class: C This exception is thrown when any sort of I/O error occurs; this only happens with the filesystem iterators. This exception has one method, C, which returns the original C<$!> that was trapped by the Iterator object. As a string, this exception provides some human-readable information along with C<$!>. =item * Internal Errors Class: C Something happened that I thought couldn't possibly happen. I would appreciate it if you could send me an email message detailing the circumstances of the error. =back =head1 REQUIREMENTS Requires the following additional module: L, v1.21 or later. =head1 SEE ALSO =over 4 =item * I, Mark Jason Dominus, Morgan Kauffman 2005. L =item * The L module, for general-purpose iterator functions. =item * The L module, for filesystem and stream iterators. =item * The L module, for iterating over a DBI record set. =item * The L module, for various oddball iterator functions. =back =head1 THANKS Much thanks to Will Coleda and Paul Lalli (and the RPI lily crowd in general) for suggestions for the pre-release version. =head1 AUTHOR / COPYRIGHT Eric J. Roode, roode@cpan.org Copyright (c) 2005 by Eric J. Roode. All Rights Reserved. This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. To avoid my spam filter, please include "Perl", "module", or this module's name in the message's subject line, and/or GPG-sign your message. =cut =begin gpg -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.1 (Cygwin) iD8DBQFDSrnpY96i4h5M0egRAg65AJ9nP1ybUFl7GgpW9sZKOAEm3UF8MQCgul3g zElCa4hIQkHXtcAwYwiEPCY= =B5j0 -----END PGP SIGNATURE----- =end gpg Iterator-0.03/Changes0000644000175000017500000000040610322363764012566 0ustar abeabeRevision history for Perl extension Iterator. 0.01 2005 August 18 - First version 0.02 2005 August 23 - Fixed several documentation typos. 0.03 2005 October 10 - Minor change to suppress a warning with newer versions of Exception::Class. Iterator-0.03/MANIFEST0000644000175000017500000000027210322363764012425 0ustar abeabeChanges Makefile.PL MANIFEST README Iterator.pm Iterator.ppd Iterator-ppm.tar.gz t/doc.t t/new.t t/value.t META.yml Module meta-data (added by MakeMaker) Iterator-0.03/README0000644000175000017500000000425410322363764012160 0ustar abeabeIterator version 0.03 ===================== This module is meant to be the definitive implementation of iterators, as popularized by Mark Jason Dominus's lectures and recent book (_Higher Order Perl_, Morgan Kauffman, 2005). An "iterator" is an object, represented as a code block that generates the "next value" of a sequence, and generally implemented as a closure. Iterator.pm provides a class that simplifies creation and use of these iterator objects. EXAMPLES Synopsis: $it = Iterator->new( sub { some code } ); Simple "upto" counter (Dominus, p. 121): sub upto { my ($m, $n) = @_; return Iterator->new( sub { return $m++ if $m <= $n; Iterator::X::Am_Now_Exhausted->throw(); }); } my $it = upto (3, 5); $i = $it->value; # returns 3 $i = $it->value; # returns 4 $i = $it->value; # returns 5 $i = $it->value; # throws an Iterator::X::Exhausted exception. $another_it = upto (7, 10); while ($another_it->isnt_exhausted) { print $another_it->value, "\n"; } # The above prints 7, 8, 9, 10 and throws no exceptions. # Another call to $another_it->value would throw an exception. DEVELOPMENT STATE This is a brand-new module. It has a decent test suite, but has not been extensively field-tested. Therefore, it should be considered "beta" software, and used with care. If you find any bugs, or if any behavior of Iterator surprises you, I would be grateful if you could send me an email message about it. Thanks. INSTALLATION To install this module, do the standard Perl module four-step: perl Makefile.PL or perl Makefile.pl LIB='my/install/path' make make test make install DEPENDENCIES This module requires these other modules and libraries: Exception::Class Test::Simple COPYRIGHT AND LICENSE Eric J. Roode, roode@cpan.org To avoid my spam filter, please include "Perl", "module", or this module's name in the message's subject line, and/or GPG-sign your message. Copyright (c) 2005 by Eric J. Roode. All Rights Reserved. This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself.