Created
March 30, 2013 22:17
-
-
Save hoehrmann/5278590 to your computer and use it in GitHub Desktop.
Generator is a sub returning an iterator when called; an iterator is a sub returning a reference to the next element, or a non-ref value when hitting the end.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package Acme::IEnumerable::List; | |
use Modern::Perl; | |
use base qw/Acme::IEnumerable/; | |
1; | |
package Acme::IEnumerable; | |
use Modern::Perl; | |
use Carp; | |
sub range { | |
my ($class, $from) = @_; | |
return bless sub { | |
return sub { | |
state $counter = $from // 0; | |
return \($counter++); | |
}; | |
}, $class; | |
} | |
sub take { | |
my ($self, $count) = @_; | |
return bless sub { | |
return sub { | |
state $left = $count; | |
return unless $left; | |
$left--; | |
state $base = $self->(); | |
my $item = $base->(); | |
return unless ref $item; | |
return $item; | |
}; | |
}, ref $self; | |
} | |
sub take_while { | |
my ($self, $predicate) = @_; | |
return bless sub { | |
return sub { | |
state $base = $self->(); | |
my $item = $base->(); | |
return unless ref $item; | |
local $_ = $$item; | |
return unless $predicate->($_); | |
}; | |
}, ref $self; | |
} | |
sub skip { | |
my ($self, $count) = @_; | |
return bless sub { | |
return sub { | |
state $base = $self->(); | |
state $left = $count; | |
while ($left) { | |
my $item = $base->(); | |
return unless ref $item; | |
$left--; | |
} | |
return $base->(); | |
}; | |
}, ref $self; | |
} | |
sub skip_while { | |
my ($self, $predicate) = @_; | |
return bless sub { | |
return sub { | |
state $base = $self->(); | |
state $skip = 1; | |
while ($skip) { | |
my $item = $base->(); | |
return unless ref $item; | |
local $_ = $$item; | |
$skip &= !! $predicate->($_); | |
return $item unless $skip; | |
} | |
return $base->(); | |
}; | |
}, ref $self; | |
} | |
sub element_at { | |
my ($self, $index) = @_; | |
croak "Index out of range for element_at" if $index < 0; | |
my $base = $self->(); | |
while (1) { | |
my $item = $base->(); | |
croak "Index out of range for element_at" unless ref $item; | |
return $$item unless $index--; | |
} | |
... | |
} | |
sub first { | |
$_[0]->element_at(0); | |
} | |
sub first_or_default { | |
my ($self, $default) = @_; | |
my $base = $self->(); | |
my $item = $base->(); | |
return $default unless ref $item; | |
return $$item; | |
} | |
sub last_or_default { | |
my ($self, $default) = @_; | |
my $base = $self->(); | |
my $item = $base->(); | |
return $default unless ref $item; | |
while (1) { | |
my $next = $base->(); | |
return $$item unless ref $next; | |
$item = $next; | |
} | |
} | |
sub count { | |
my ($self, $predicate) = @_; | |
$predicate //= sub { 1 }; | |
my $base = $self->(); | |
while (1) { | |
state $counter = 0; | |
my $item = $base->(); | |
return $counter unless ref $item; | |
local $_ = $$item; | |
$counter += !! $predicate->($_); | |
} | |
... | |
} | |
sub select { | |
my ($self, $projection) = @_; | |
return bless sub { | |
return sub { | |
state $base = $self->(); | |
my $item = $base->(); | |
return unless ref $item; | |
local $_ = $$item; | |
return \($projection->($_)); | |
}; | |
}, ref $self; | |
} | |
sub where { | |
my ($self, $predicate) = @_; | |
return bless sub { | |
return sub { | |
state $base = $self->(); | |
while (1) { | |
my $item = $base->(); | |
return unless ref $item; | |
local $_ = $$item; | |
next unless $predicate->($_); | |
return $item; | |
} | |
}; | |
}, ref $self; | |
} | |
sub aggregate { | |
my $self = shift; | |
my $base = $self->(); | |
my ($func, $seed); | |
if (@_ == 1) { | |
$func = shift; | |
my $item = $base->(); | |
croak unless ref $item; | |
$seed = $$item; | |
} elsif (@_ == 2) { | |
$seed = shift; | |
$func = shift; | |
} else { | |
... | |
} | |
while (1) { | |
my $item = $base->(); | |
return $seed unless ref $item; | |
$seed = $func->($seed, $$item); | |
} | |
... | |
} | |
sub min { | |
my ($self) = @_; | |
return $self->aggregate(sub { | |
$_[0] < $_[1] ? $_[0] : $_[1] | |
}); | |
} | |
sub max { | |
my ($self) = @_; | |
return $self->aggregate(sub { | |
$_[0] > $_[1] ? $_[0] : $_[1] | |
}); | |
} | |
sub all { | |
my ($self, $predicate) = @_; | |
my $base = $self->(); | |
while (1) { | |
my $item = $base->(); | |
return 1 unless ref $item; | |
local $_ = $$item; | |
return 0 unless $predicate->($_); | |
} | |
... | |
} | |
sub any { | |
my ($self, $predicate) = @_; | |
$predicate //= sub { 1 }; | |
my $base = $self->(); | |
while (1) { | |
my $item = $base->(); | |
return 0 unless ref $item; | |
local $_ = $$item; | |
return 1 if $predicate->($_); | |
} | |
... | |
} | |
sub sum { | |
my $self = shift; | |
return $self->aggregate(0, sub { $_[0] + $_[1] }); | |
} | |
sub to_list { | |
my $self = shift; | |
my @result; | |
my $enum = $self->(); | |
for (my $item = $enum->(); ref $item; $item = $enum->()) { | |
push @result, $$item; | |
} | |
@result; | |
} | |
sub to_array { | |
to_list(@_); | |
} | |
1; | |
package main; | |
use Modern::Perl; | |
use YAML::XS; | |
use Data::Dumper; | |
my $v1 = Acme::IEnumerable->range(1); | |
my $v2 = $v1; | |
my $i1 = $v1->(); | |
my $i2 = $v2->(); | |
say ${ $i1->() }; | |
say ${ $i1->() }; | |
say ${ $i1->() }; | |
say ""; | |
say ${ $i2->() }; | |
say ${ $i2->() }; | |
say ${ $i2->() }; | |
say Dumper $i2; | |
say join '', | |
Acme::IEnumerable | |
->range(1) | |
->select(sub { $_ ** 3 }) | |
->select(sub { sprintf "%6.0f\n", $_ }) | |
->take(20) | |
->to_list; | |
say "---"; | |
say Acme::IEnumerable | |
->range(1) | |
->take(100) | |
->aggregate(sub { $_[0] + $_[1] }); | |
say join "\n", Acme::IEnumerable | |
->range(1) | |
->take(20) | |
->skip_while(sub { $_ <= 10 }) | |
->max; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment