FunctionalPerl
view release on metacpan or search on metacpan
lib/FP/Stream.pm view on Meta::CPAN
This is alpha software! Read the status section in the package README
or on the L<website|http://functional-perl.org/>.
=cut
package FP::Stream;
use strict;
use warnings;
use warnings FATAL => 'uninitialized';
use Exporter "import";
our @EXPORT = qw(
is_null
Keep
Weakened
stream_iota
stream_range
stream_step_range
stream_length
stream_append
stream_map
stream_map_with_tail
stream_filter
stream_filter_with_tail
stream_fold
stream_foldr1
stream_fold_right
stream_state_fold_right
stream__array_fold_right
stream__string_fold_right
stream__subarray_fold_right stream__subarray_fold_right_reverse
stream_sum
array_to_stream stream
subarray_to_stream subarray_to_stream_reverse
string_to_stream
stream_to_string
stream_strings_join
stream_for_each
stream_drop
stream_take
stream_take_while
stream_slice
stream_drop_while
stream_ref
stream_zip2
stream_zip
stream_zip_with
stream_to_array
stream_to_purearray
stream_to_list
stream_sort
stream_group
stream_mixed_flatten
stream_mixed_fold_right
stream_mixed_state_fold_right
stream_any
stream_show
);
our @EXPORT_OK = qw(
F weaken
cons car cdr first rest
stream_cartesian_product
stream_cartesian_product_2
);
our %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]);
use FP::Lazy;
use FP::List ":all";
use FP::Combinators qw(flip flip2of3 rot3right rot3left);
use Chj::TEST;
use FP::Weak;
use FP::Predicates 'is_natural0';
use FP::Show;
use FP::fix;
use Carp;
use FP::Carp;
use FP::Docstring;
sub stream_iota {
@_ <= 2 or fp_croak_arity 2;
my ($maybe_start, $maybe_n) = @_;
my $start = $maybe_start // 0;
if (defined $maybe_n) {
my $end = $start + $maybe_n;
my $rec;
$rec = sub {
my ($i) = @_;
my $rec = $rec;
lazy {
if ($i < $end) {
cons($i, &$rec($i + 1))
} else {
null
}
}
};
@_ = ($start);
goto &{ Weakened $rec};
} else {
my $rec;
$rec = sub {
my ($i) = @_;
my $rec = $rec;
lazy {
cons($i, &$rec($i + 1))
}
};
@_ = ($start);
goto &{ Weakened $rec};
}
}
# Like perl's `..`
sub stream_range {
@_ <= 2 or fp_croak_arity 2;
my ($maybe_start, $maybe_end) = @_;
my $start = $maybe_start // 0;
stream_iota($start, defined $maybe_end ? $maybe_end + 1 - $start : undef);
}
lib/FP/Stream.pm view on Meta::CPAN
} else {
my $rec;
$rec = sub {
my ($i) = @_;
my $rec = $rec;
lazy {
cons($i, &$rec($i + $step))
}
};
@_ = ($start);
goto &{ Weakened $rec};
}
}
TEST { stream_step_range(-1)->take(4)->array }
[0, -1, -2, -3];
TEST { stream_step_range(2, 3, 6)->array }
[3, 5];
TEST { stream_step_range(2, 3, 7)->array }
[3, 5, 7];
TEST { stream_step_range(-1, 3, 1)->array }
[3, 2, 1];
sub stream_length;
*stream_length = FP::List::make_length(1);
*FP::List::List::stream_length = \&stream_length;
TEST {
my $s = stream_iota->take(10);
my $l = $s->length;
[$l, $s]
}
[10, undef];
# left fold, sometimes called `foldl` (or with changes, `reduce`,
# which is defined in FP::Abstract::Sequence)
# All functional languages seem to agree that right fold should pass
# the state argument as the second argument (to the right) to the
# build function (i.e. f(element,state)). But they don't when it comes
# to left fold: Scheme (SRFI-1) still calls f(element,state), whereas
# Haskell and Ocaml call f(state,element), perhaps according to the
# logic that both the walking order (which element of the list should
# be combined with the original state first?) and the argument order
# should be flipped, perhaps to try make them visually accordant. But
# then Ocaml also flips the start and input argument order in
# `fold_left`, so this one is different anyway. And Clojure calls it
# `reduce`, and has a special case when only two arguments are
# provided. Kinda hopeless?
# The author of this library decided to stay with the Scheme
# tradition: it seems to make sense to keep the argument order the
# same (as determined by the type of the arguments) for both kinds of
# fold. If nothing more this allows `*cons` to be passed as $fn
# directly to construct a list.
sub stream_fold {
@_ == 3 or fp_croak_arity 3;
my ($fn, $start, $l) = @_;
weaken $_[2];
my $v;
LP: {
$l = force $l;
if (is_pair $l) {
($v, $l) = first_and_rest $l;
$start = &$fn($v, $start);
redo LP;
}
}
$start
}
*FP::List::List::stream_fold = rot3left \&stream_fold;
TEST {
stream_fold sub { $_[0] + $_[1] }, 5, stream_iota(10, 2)
}
5 + 10 + 11;
# and actually argument order dependent tests:
TEST {
stream_fold sub { [@_] }, 0, stream(1, 2)
}
[2, [1, 0]];
TEST { stream_fold(\&cons, null, stream(1, 2))->array }
[2, 1];
sub stream_sum {
@_ == 1 or fp_croak_arity 1;
my ($s) = @_;
weaken $_[0];
stream_fold(sub { $_[0] + $_[1] }, 0, $s)
}
# XXX sum is in FP::Abstract::Sequence now, although not weakening.. soo ?
*FP::List::List::stream_sum = \&stream_sum;
TEST {
stream_iota->map(sub { $_[0] * $_[0] })->take(5)->sum
}
30;
# leak test: XXX make this into a LEAK_TEST or so form(s), and env var, ok?
#TEST{ stream_iota->rest->take(100000)->sum }
# 5000050000;
sub stream_append2 {
@_ == 2 or fp_croak_arity 2;
my ($l1, $l2) = @_;
weaken $_[0];
weaken $_[1];
lazyT {
$l1 = force $l1;
is_null($l1) ? $l2 : cons(car($l1), stream_append(cdr($l1), $l2))
}
"FP::List::List"
}
sub stream_append {
if (@_ == 0) {
null
} elsif (@_ == 1) {
$_[0]
} elsif (@_ == 2) {
goto \&stream_append2
} else {
my $ss = list(reverse @_);
weaken $_ for @_;
# ^ WHEN is this even needed, I think it is when calling a
# stream consumer afterwards, right?
$ss->rest->fold(
sub {
my ($s, $rest) = @_;
lazyT {
stream_append2($s, $rest)
}
"FP::List::List"
},
$ss->first
)
}
}
*FP::List::List::stream_append = \&stream_append;
TEST {
stream_to_string(stream_append string_to_stream("Hello"),
string_to_stream(" World"))
}
'Hello World';
# F is defined further down, thus call with parens
TEST { F(stream_append list(qw(a b)), list(qw(c d)), list(qw(e f))) }
GIVES { list('a', 'b', 'c', 'd', 'e', 'f') };
TEST { F(stream_append list(qw(a b)), list(qw(c d))) }
GIVES { list('a', 'b', 'c', 'd') };
TEST { F(stream_append list(qw(a b))) }
GIVES { list('a', 'b') };
TEST { F(stream_append) }
list();
TEST {
my @a;
my $s = stream_append(
lazy { push @a, "a"; list(qw(a b)) },
lazy { push @a, "b"; list(qw(c d)) },
lazy { push @a, "c"; list(qw(e f)) }
);
my @r;
F(Keep($s)->take(1));
push @r, ["take 1", @a];
F(Keep($s)->take(2));
push @r, ["take 2", @a];
F(Keep($s)->take(3));
push @r, ["take 3", @a];
F(Keep($s)->take(4));
push @r, ["take 4", @a];
F(Keep($s)->take(5));
push @r, ["take 5", @a];
F(Keep($s)->take(6));
push @r, ["take 6", @a];
\@r
}
[
["take 1", "a"],
["take 2", "a"],
["take 3", "a", "b"],
["take 4", "a", "b"],
["take 5", "a", "b", "c"],
["take 6", "a", "b", "c"]
];
sub stream_map {
@_ == 2 or fp_croak_arity 2;
my ($fn, $l) = @_;
weaken $_[1];
lazyT {
$l = force $l;
is_null($l) ? null : cons(&$fn(car $l), stream_map($fn, cdr $l))
}
"FP::List::List"
}
*FP::List::List::stream_map = flip \&stream_map;
sub stream_map_with_tail {
@_ == 3 or fp_croak_arity 3;
my ($fn, $l, $tail) = @_;
weaken $_[1];
lazyT {
$l = force $l;
is_null($l)
? $tail
: cons(&$fn(car $l), stream_map_with_tail($fn, cdr($l), $tail))
}
"FP::Abstract::Sequence"
}
*FP::List::List::stream_map_with_tail = flip2of3 \&stream_map_with_tail;
# 2-ary (possibly slightly faster) version of stream_zip
sub stream_zip2 {
@_ == 2 or fp_croak_arity 2;
my ($l, $m) = @_;
do { weaken $_ if is_promise $_ }
for @_; #needed?
lazyT {
$l = force $l;
$m = force $m;
(is_null $l or is_null $m)
? null
: cons([car($l), car($m)], stream_zip2(cdr($l), cdr($m)))
}
"FP::List::List"
}
*FP::List::List::stream_zip2 = \&stream_zip2;
# n-ary version of stream_zip2
sub stream_zip {
my @ps = @_;
do { weaken $_ if is_promise $_ }
for @_; #needed?
lazyT {
my @vs = map {
my $v = force $_;
is_null($v) ? return null : $v
} @ps;
my $a = [map { car $_ } @vs];
my $b = stream_zip(map { cdr $_ } @vs);
cons($a, $b)
}
"FP::List::List"
}
*FP::List::List::stream_zip = \&stream_zip; # XX fall back on zip2
# for 2 arguments?
sub stream_zip_with {
my ($f, $l1, $l2) = @_;
weaken $_[1];
weaken $_[2];
lazyT {
my $l1 = force $l1;
my $l2 = force $l2;
(is_null $l1 or is_null $l2) ? null : cons &$f(car($l1), car($l2)),
stream_zip_with($f, cdr($l1), cdr($l2))
}
"FP::List::List"
}
*FP::List::List::stream_zip_with = flip2of3 \&stream_zip_with;
sub stream_filter;
*stream_filter = FP::List::make_filter(1);
*FP::List::List::stream_filter = flip \&stream_filter;
sub stream_filter_with_tail;
*stream_filter_with_tail = FP::List::make_filter_with_tail(1);
*FP::List::List::stream_filter_with_tail = flip2of3 \&stream_filter_with_tail;
# http://hackage.haskell.org/package/base-4.7.0.2/docs/Prelude.html#v:foldr1
# foldr1 is a variant of foldr that has no starting value argument,
# and thus must be applied to non-empty lists.
sub stream_foldr1 {
@_ == 2 or fp_croak_arity 2;
my ($fn, $l) = @_;
weaken $_[1];
lazy {
$l = force $l;
if (is_pair $l) {
&$fn(car($l), stream_foldr1($fn, cdr($l)))
} elsif (is_null $l) {
die "foldr1: reached end of list"
} else {
die "improper list: $l"
}
}
}
*FP::List::List::stream_foldr1 = flip \&stream_foldr1;
sub stream_fold_right {
@_ == 3 or fp_croak_arity 3;
my ($fn, $start, $l) = @_;
weaken $_[2];
lazy {
$l = force $l;
if (is_pair $l) {
&$fn(car($l), stream_fold_right($fn, $start, cdr $l))
} elsif (is_null $l) {
$start
} else {
die "improper list: $l"
}
}
}
*FP::List::List::stream_fold_right = rot3left \&stream_fold_right;
*FP::List::List::stream_preferred_fold = \&FP::List::List::stream_fold_right;
sub make_stream__fold_right {
my ($length, $ref, $start, $d, $whileP) = @_;
sub {
@_ == 3 or fp_croak_arity 3;
my ($fn, $tail, $a) = @_;
my $len = &$length($a);
my $rec;
$rec = sub {
my ($i) = @_;
my $rec = $rec;
lazy {
if (&$whileP($i, $len)) {
&$fn(&$ref($a, $i), &$rec($i + $d))
} else {
$tail
}
}
};
&{ Weakened $rec}($start)
}
}
our $lt = sub { $_[0] < $_[1] };
our $gt = sub { $_[0] > $_[1] };
our $array_length = sub { scalar @{ $_[0] } };
our $array_ref = sub { $_[0][$_[1]] };
our $string_length = sub { length $_[0] };
our $string_ref = sub { substr $_[0], $_[1], 1 };
sub stream__array_fold_right;
*stream__array_fold_right
= make_stream__fold_right($array_length, $array_ref, 0, 1, $lt);
# XX export these array functions as methods to ARRAY library
sub stream__string_fold_right;
*stream__string_fold_right
= make_stream__fold_right($string_length, $string_ref, 0, 1, $lt);
sub stream__subarray_fold_right {
@_ == 5 or fp_croak_arity 5;
my ($fn, $tail, $a, $start, $maybe_end) = @_;
make_stream__fold_right($array_length, $array_ref, $start, 1,
defined $maybe_end ? sub { $_[0] < $_[1] and $_[0] < $maybe_end } : $lt)
->($fn, $tail, $a);
}
sub stream__subarray_fold_right_reverse {
@_ == 5 or fp_croak_arity 5;
my ($fn, $tail, $a, $start, $maybe_end) = @_;
make_stream__fold_right($array_length, $array_ref, $start, -1,
defined $maybe_end
? sub { $_[0] >= 0 and $_[0] > $maybe_end }
: sub { $_[0] >= 0 })->($fn, $tail, $a);
}
sub array_to_stream {
@_ >= 1 and @_ <= 2 or fp_croak_arity "1-2";
my ($a, $maybe_tail) = @_;
stream__array_fold_right(\&cons, $maybe_tail // null, $a)
}
sub stream {
array_to_stream [@_]
}
sub subarray_to_stream {
@_ >= 2 and @_ <= 4 or fp_croak_arity "2-4";
my ($a, $start, $maybe_end, $maybe_tail) = @_;
stream__subarray_fold_right(\&cons, $maybe_tail // null,
$a, $start, $maybe_end)
}
sub subarray_to_stream_reverse {
@_ >= 2 and @_ <= 4 or fp_croak_arity "2-4";
my ($a, $start, $maybe_end, $maybe_tail) = @_;
stream__subarray_fold_right_reverse(\&cons, $maybe_tail // null,
$a, $start, $maybe_end)
}
sub string_to_stream {
@_ >= 1 and @_ <= 2 or fp_croak_arity "1-2";
my ($str, $maybe_tail) = @_;
stream__string_fold_right(\&cons, $maybe_tail // null, $str)
}
sub stream_to_string {
@_ == 1 or fp_croak_arity 1;
my ($l) = @_;
weaken $_[0];
my $str = "";
while (($l = force $l), !is_null $l) {
$str .= car $l;
$l = cdr $l;
}
$str
}
*FP::List::List::stream_string = \&stream_to_string;
TEST { stream("Ha", "ll", "o")->string } "Hallo";
sub stream_strings_join {
@_ == 2 or fp_croak_arity 2;
my ($l, $val) = @_;
weaken $_[0];
# XX can't I just use list_strings_join? no, the other way round
# would work.
# depend on FP::Array. Lazily, for depencency cycle?
require FP::Array;
FP::Array::array_strings_join(stream_to_array($l), $val);
}
*FP::List::List::stream_strings_join = \&stream_strings_join;
TEST { stream(1, 2, 3)->strings_join("-") }
"1-2-3";
sub stream_for_each;
*stream_for_each = FP::List::make_for_each(1, 0);
sub stream_for_each_with_islast;
*stream_for_each_with_islast = FP::List::make_for_each(1, 1);
*FP::List::List::stream_for_each = flip \&stream_for_each;
sub stream_drop {
@_ == 2 or fp_croak_arity 2;
my ($s, $n) = @_;
weaken $_[0];
while ($n > 0) {
$s = force $s;
die "stream too short" if is_null $s;
$s = cdr $s;
$n--
}
$s
}
*FP::List::List::stream_drop = \&stream_drop;
sub stream_take {
@_ == 2 or fp_croak_arity 2;
my ($s, $n) = @_;
weaken $_[0];
lazyT {
if ($n > 0) {
$s = force $s;
is_null($s) ? $s : cons(car($s), stream_take(cdr($s), $n - 1));
} else {
null
}
}
"FP::List::List"
}
*FP::List::List::stream_take = \&stream_take;
sub stream_take_while {
@_ == 2 or fp_croak_arity 2;
my ($fn, $s) = @_;
weaken $_[1];
lazyT {
$s = force $s;
if (is_null $s) {
null
} else {
my $a = car $s;
if (&$fn($a)) {
cons $a, stream_take_while($fn, cdr $s)
} else {
null
}
}
}
"FP::List::List"
}
*FP::List::List::stream_take_while = flip \&stream_take_while;
sub stream_slice {
@_ == 2 or fp_croak_arity 2;
my ($start, $end) = @_;
weaken $_[0];
weaken $_[1];
$end = force $end;
my $rec;
$rec = sub {
my ($s) = @_;
weaken $_[0];
my $rec = $rec;
lazyT {
$s = force $s;
if (is_null $s) {
$s # null
} else {
if ($s eq $end) {
null
} else {
cons car($s), &$rec(cdr $s)
}
}
}
"FP::List::List"
};
@_ = ($start);
goto &{ Weakened $rec};
}
*FP::List::List::stream_slice = \&stream_slice;
# maybe call it `cut_at` instead?
sub stream_drop_while {
@_ == 2 or fp_croak_arity 2;
my ($pred, $s) = @_;
weaken $_[1];
lazyT {
LP: {
$s = force $s;
if (!is_null $s and &$pred(car $s)) {
$s = cdr $s;
redo LP;
} else {
$s
}
}
}
"FP::Abstract::Sequence"
}
*FP::List::List::stream_drop_while = flip \&stream_drop_while;
sub stream_ref;
*stream_ref = FP::List::make_ref(1);
*FP::List::List::stream_ref = \&stream_ref;
sub exn (&) {
@_ == 1 or fp_croak_arity 1;
my ($thunk) = @_;
eval { &$thunk(); '' } // do { $@ =~ /(.*?) at/; $1 }
}
sub t_ref {
my ($list, $cons, $liststream) = @_;
TEST {
my $l = &$list(qw(a b));
my $il = &$cons("x", "y");
[
Keep($l)->ref(0), Keep($l)->ref(1), exn { Keep($l)->ref(-1) },
exn { Keep($l)->ref(0.1) }, exn { Keep($l)->ref(2) },
Keep($il)->ref(0),
exn { Keep($il)->ref(1) }
]
}
[
"a", "b",
"invalid index: -1",
"invalid index: '0.1'",
"requested element 2 of $liststream of length 2",
"x", "improper $liststream"
];
}
t_ref \&list, \&cons, "list";
t_ref \&stream, sub {
my ($a, $r) = @_;
lazy { cons $a, $r }
}, "stream";
TEST { list_ref cons(0, stream(1, 2, 3)), 2 } 2;
TEST { stream_ref cons(0, stream(1, 2, 3)), 2 } 2;
# force everything deeply
sub F {
@_ == 1 or fp_croak_arity 1;
my ($v) = @_;
#weaken $_[0]; since I usually use it interactively, and should
# only be good for short sequences, better don't
if (is_promise $v) {
F(force $v);
} else {
if (length(my $r = ref $v)) {
if (is_pair $v) {
cons(F(car $v), F(cdr $v))
} elsif (is_null $v) {
$v
} elsif ($r eq "ARRAY") {
[map { F($_) } @$v]
} elsif (UNIVERSAL::isa($v, "ARRAY")) {
bless [map { F($_) } @$v], ref $v
} else {
$v
}
} else {
$v
}
}
}
sub stream_to_array {
@_ == 1 or fp_croak_arity 1;
my ($l) = @_;
weaken $_[0];
my $res = [];
my $i = 0;
$l = force $l;
while (!is_null $l) {
my $v = car $l;
$$res[$i] = $v;
$l = force cdr $l;
$i++;
}
$res
}
*FP::List::List::stream_array = \&stream_to_array;
sub stream_to_purearray {
my ($l) = @_;
weaken $_[0];
my $a = stream_to_array $l;
require FP::PureArray;
FP::PureArray::array_to_purearray($a)
}
*FP::List::List::stream_purearray = \&stream_to_purearray;
TEST {
stream(1, 3, 4)->purearray->map (sub { $_[0]**2 })
}
bless [1, 9, 16], "FP::_::PureArray";
sub stream_to_list {
@_ == 1 or fp_croak_arity 1;
my ($l) = @_;
weaken $_[0];
array_to_list stream_to_array $l
}
*FP::List::List::stream_list = \&stream_to_list;
sub stream_sort {
@_ == 1 or @_ == 2 or fp_croak_arity "1 or 2";
my ($l, $maybe_cmp) = @_;
stream_to_purearray($l)->sort ($maybe_cmp)
}
*FP::List::List::stream_sort = \&stream_sort;
TEST {
require FP::Ops;
stream(5, 3, 8, 4)->sort (\&FP::Ops::real_cmp)->array
}
[3, 4, 5, 8];
TEST { ref(stream(5, 3, 8, 4)->sort (\&FP::Ops::real_cmp)) }
'FP::_::PureArray'; # XX ok? Need to `->stream` if a stream is needed
TEST { stream(5, 3, 10, 8, 4)->sort (\&FP::Ops::real_cmp)->stream->car }
3;
# but then PureArray has `first`, too, if that's all you need.
TEST { stream(5, 3, 10, 8, 4)->sort->stream->car }
10; # the default is string sort
# add a lazy merge sort instead/in addition?
sub stream_group;
*stream_group = FP::List::make_group(1);
sub FP::List::List::stream_group {
__
'group($self, $equal, $maybe_tail): build groups of subsequent items that are $equal.';
@_ >= 2 and @_ <= 3 or fp_croak_arity "2-3";
my ($self, $equal, $maybe_tail) = @_;
weaken $_[0];
stream_group($equal, $self, $maybe_tail)
}
sub stream_split;
*stream_split = FP::List::make_split(1);
sub stream_mixed_flatten {
@_ >= 1 and @_ <= 3 or fp_croak_arity "1-3";
my ($v, $maybe_tail, $maybe_delay) = @_;
#XXX needed, no? weaken $_[0] if ref $_[0];
mixed_flatten($v, $maybe_tail // null, $maybe_delay || \&lazyLight)
}
*FP::List::List::stream_mixed_flatten = \&stream_mixed_flatten;
sub stream_any {
@_ == 2 or @_ == 3 or fp_croak_arity "2 or 3";
my ($pred, $l, $prev_false) = @_;
weaken $_[1];
$l = force $l;
if (is_pair $l) {
my $v = $pred->(car $l);
$v or do {
my $r = cdr $l;
stream_any($pred, $r, $v)
}
} elsif (is_null $l) {
$prev_false
} else {
die "improper list: $l"
}
}
*FP::List::List::stream_any = flip \&stream_any;
# (meant as a debugging tool: turn stream to string)
sub stream_show {
@_ == 1 or fp_croak_arity 1;
my ($s) = @_;
join("", map {" '$_'\n"} @{ stream_to_array $s })
}
*FP::List::List::stream_show = \&stream_show;
# A stateful fold: collect state while going to the end (starting from
# the left). Then it's basically a fold_right.
sub stream_state_fold_right {
@_ == 3 or fp_croak_arity 3;
my ($fn, $stateupfn, $s) = @_;
sub {
@_ == 1 or fp_croak_arity 1;
my ($statedown) = @_;
no warnings 'recursion';
FORCE $s;
if (is_null $s) {
@_ = ($statedown);
goto &$stateupfn
} else {
my ($v, $s) = $s->first_and_rest;
@_ = ($v, $statedown, stream_state_fold_right($fn, $stateupfn, $s));
goto &$fn;
}
}
}
*FP::List::List::stream_state_fold_right = rot3left \&stream_state_fold_right;
TEST {
stream_state_fold_right(
sub {
my ($v, $statedown, $restfn) = @_;
[$v, &$restfn($statedown . ".")]
},
sub {
my ($statedown) = @_;
$statedown . "end"
},
stream(3, 4)
lib/FP/Stream.pm view on Meta::CPAN
[3, [4, "start..end"]];
TEST {
stream_state_fold_right(
sub {
my ($v, $statedown, $restfn) = @_;
cons $v, &$restfn($statedown)
},
sub { $_[0] }, # \&identity
stream(3, 4)
)->(list 5, 6)->array
}
[3, 4, 5, 6];
TEST {
stream_state_fold_right(
sub {
my ($v, $statedown, $restfn) = @_;
cons $v, &$restfn(cons $v, $statedown)
},
sub { $_[0] }, # \&identity
stream(3, 4)
)->(list 5, 6)->array
}
[3, 4, 4, 3, 5, 6];
TEST {
stream_state_fold_right(
sub {
my ($v, $statedown, $restfn) = @_;
lazy {
cons [$v, $statedown], &$restfn($statedown + 1)
}
},
undef, # \&identity, but never reached
stream_iota
)->(10)->take(3)->array
}
[[0, 10], [1, 11], [2, 12]];
# modified test from above
TEST {
stream_iota->state_fold_right(
sub {
my ($v, $statedown, $restfn) = @_;
lazy {
cons [$v, $statedown], &$restfn($statedown + 1)
}
},
undef, # \&identity, but never reached
)->(10)->take(3)->array
}
[[0, 10], [1, 11], [2, 12]];
# are these warranted?: (or should they be deprecated right upon
# introduction?)
sub stream_mixed_fold_right {
@_ == 3 or fp_croak_arity 3;
my ($fn, $state, $v) = @_;
weaken $_[2] if ref $_[2];
@_ = ($fn, $state, stream_mixed_flatten $v);
goto \&stream_fold_right
}
*FP::List::List::stream_mixed_fold_right = rot3left \&stream_mixed_fold_right;
sub stream_mixed_state_fold_right {
@_ == 3 or fp_croak_arity 3;
my ($fn, $statefn, $v) = @_;
weaken $_[2] if ref $_[2];
@_ = ($fn, $statefn, stream_mixed_flatten $v);
goto \&stream_state_fold_right
}
*FP::List::List::stream_mixed_state_fold_right
= rot3left \&stream_mixed_state_fold_right;
# 'cross product'
sub stream_cartesian_product_2 {
@_ == 2 or fp_croak_arity 2;
my ($a, $orig_b) = @_;
weaken $_[0];
weaken $_[1];
my $rec;
$rec = sub {
my ($a, $b) = @_;
my $rec = $rec;
lazyT {
if (is_null $a) {
null
} elsif (is_null $b) {
&$rec(cdr($a), $orig_b);
} else {
cons(cons(car($a), car($b)), &$rec($a, cdr $b))
}
}
"FP::List::List"
};
Weakened($rec)->($a, $orig_b)
}
*FP::List::List::stream_cartesian_product_2 = \&stream_cartesian_product_2;
TEST { F stream_cartesian_product_2 list("A", "B"), list(list(1), list(2)) }
list(list('A', 1), list('A', 2), list('B', 1), list('B', 2));
TEST {
F stream_cartesian_product_2 list("E", "F"),
stream_cartesian_product_2 list("C", "D"),
list(list("A"), list("B"))
}
list(
list("E", "C", "A"),
list("E", "C", "B"),
list("E", "D", "A"),
list("E", "D", "B"),
list("F", "C", "A"),
list("F", "C", "B"),
list("F", "D", "A"),
list("F", "D", "B")
);
sub stream_cartesian_product {
my @v = @_;
weaken $_ for @_;
if (!@v) {
die "stream_cartesian_product: need at least 1 argument"
} elsif (@v == 1) {
stream_map \&list, $v[0]
} else {
my $first = shift @v;
stream_cartesian_product_2($first, stream_cartesian_product(@v))
}
}
*FP::List::List::stream_cartesian_product = \&stream_cartesian_product;
TEST_STDOUT {
write_sexpr stream_cartesian_product list("A", "B"), list("C", "D"),
list("E", "F")
}
'(("A" "C" "E") ("A" "C" "F") ("A" "D" "E") ("A" "D" "F") ("B" "C" "E")'
. ' ("B" "C" "F") ("B" "D" "E") ("B" "D" "F"))';
TEST_STDOUT {
write_sexpr stream_cartesian_product list("A", "B"), list("C", "D"),
list("E")
}
'(("A" "C" "E") ("A" "D" "E") ("B" "C" "E") ("B" "D" "E"))';
TEST_STDOUT {
write_sexpr stream_cartesian_product list("A", "B"), list("C", "D")
}
'(("A" "C") ("A" "D") ("B" "C") ("B" "D"))';
TEST_STDOUT {
write_sexpr stream_cartesian_product list("A", "B")
}
'(("A") ("B"))';
TEST { F stream(1, 2)->cartesian_product(list 3, 4) }
list(list(1, 3), list(1, 4), list(2, 3), list(2, 4));
# chunksOf from Haskell's Data.List.Split, but returns `purearray`s
# instead of lists.
sub make_chunks_of {
my ($strictly, $lazy) = @_;
sub {
@_ == 2 or fp_croak_arity 2;
my ($s, $chunklen) = @_;
$chunklen >= 1 or croak "invalid chunklen: $chunklen";
weaken $_[0] if $lazy; # although tie down is only chunk sized
require FP::PureArray;
fix(
sub {
my ($rec, $s) = @_;
weaken $_[0] if $lazy; # although tie down is only chunk sized
lazy_if {
return null if $s->is_null;
my @v;
for my $i (1 .. $chunklen) {
if ($s->is_null) {
die "premature end of input" if $strictly;
} else {
push @v, $s->first;
$s = $s->rest;
}
}
cons FP::PureArray::array_to_purearray(\@v), &$rec($s)
}
$lazy
}
)->($s)
}
}
# Do we still want functions?
#*stream_chunks_of = flip make_chunks_of(0);
#*stream_strictly_chunks_of = flip make_chunks_of(0);
*FP::List::List::chunks_of = make_chunks_of(0, 0);
*FP::List::List::strictly_chunks_of = make_chunks_of(1, 0);
*FP::List::List::stream_chunks_of = make_chunks_of(0, 1);
*FP::List::List::stream_strictly_chunks_of = make_chunks_of(1, 1);
TEST { list(qw(a b c d e f))->chunks_of(2) }
GIVES {
require FP::PureArray;
import FP::PureArray;
list(purearray('a', 'b'), purearray('c', 'd'), purearray('e', 'f'))
};
TEST { stream(qw(a b c d e f))->chunks_of(3)->F }
GIVES {
list(purearray('a', 'b', 'c'), purearray('d', 'e', 'f'))
};
TEST { list(qw(a b c d e f))->chunks_of(4) }
GIVES {
list(purearray('a', 'b', 'c', 'd'), purearray('e', 'f'))
};
TEST { list(qw(a b c d e f))->chunks_of(40) }
GIVES {
list(purearray('a', 'b', 'c', 'd', 'e', 'f'))
};
TEST_EXCEPTION { list(qw(a b c d e f))->strictly_chunks_of(4)->F }
'premature end of input';
# ----- Tests ----------------------------------------------------------
TEST {
stream_any sub { $_[0] % 2 }, array_to_stream [2, 4, 8]
}
0;
TEST {
stream_any sub { $_[0] % 2 }, array_to_stream []
}
undef;
TEST {
( run in 0.933 second using v1.01-cache-2.11-cpan-39bf76dae61 )