#!/usr/bin/perl

# Idiom #319 generator functions

use feature 'say';
use strict;

# predeclare _upto with prototype (&) so it expects an anonymous sub
# without this, the iterator won't work inside a foreach loop (only while)
# see https://p...content-available-to-author-only...l.org/perlsub#Prototypes
sub _upto (&) { return $_[0]; }  

# define a closure over $start and $end
# see https://p...content-available-to-author-only...l.org/perlfaq7#What%27s-a-closure?
sub upto {
    my ($start, $end) = @_;

    my $n = $start;

    return _upto {
        return
            # in an array context, return list $start..$end
            # in scalar context"
            #   if we've reached the end, reset $n to $start and return an empty list
            #   (which in scalar context will be taken as false and stop the loop)
            #   otherwise increment $n and return it
            wantarray ? $start .. $end    :
            $n > $end ? ($n = $start, ()) :
            $n++
            ;
        };
}

my $it = upto(3, 5);

print 'foreach:';
foreach ( $it->() ) { print ' ' . $_ }
print "\n";

$it = upto(2, 6);

print 'while:  ';
while ( my $n = $it->() ) { print ' ' . $n }
print "\n";

# In array context, upto can be used to generate a range.
# For that it uses perl's .. operator to generate an actual
# list of numbers,  So this use case isn't an example of an 
# iterator.  The functionality is provided in upto just so it
# would be a bit more generally useful.
my $range = upto(7, 11);
print 'list context: ';
my @list = $range->();
print join ' ', @list;
print "\n";
