Current section
Files
Jump to
Current section
Files
src/lazy_lists.erl
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% @doc Lazy lists
%%% @end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-module(lazy_lists).
%%%_* Exports ==========================================================
-export([new/1, new/2]).
-export([head/1]).
-export([tail/1]).
-export([last/1]).
-export([filter/2]).
-export([partition/2]).
-export([nth/2, nth_/2]).
-export([take/2, take_/2]).
-export([drop/2]).
-export([drop_while/2]).
-export([take_while/2, take_while_/2]).
-export([map/2]).
-export([foreach/2]).
-export([fold/3]).
-export([uncons/1]).
-export([expand/1]).
-export([limit/2]).
-export([while/2]).
-export([zip/2]).
-export([unzip/1]).
-export([cons/2]).
-export([append/2]).
-export([sum/1]).
-export([length/1]).
-export([chunk/2]).
-export([every/2]).
-export([interleave/2]).
-export([join/2]).
-export([cycle/1]).
-export([dedup/1]).
-export([member/2]).
-export([max/1]).
-export([min/1]).
-export([any/2]).
-export([all/2]).
-export([find/2]).
-export([empty/0]).
-export([delay/2]).
%%%_* Generators -------------------------------------------------------
-export([duplicate/1, duplicate/2]).
-export([seq/0, seq/1, seq/2, seq/3]).
-export([rand/0, rand/1, rand/2]).
-export([fib/0]).
-export([perms/1]).
-export([read_lines/1]).
%%%_* Types ------------------------------------------------------------
-export_type([lazy_list/0, lazy_list/1]).
%%%_* Records ==========================================================
-record(lazy_list, { gen = error(gen) :: lazy_gens:gen()
, acc = error(acc) :: term()
}).
%%%_* Types ============================================================
-opaque lazy_list() :: #lazy_list{}.
-opaque lazy_list(T) :: #lazy_list{gen :: lazy_gens:gen(T)}.
-type maybe_lazy_list(T) :: [T] | lazy_list(T) | lazy_gens:gen(T).
-type predicate(T) :: fun((T) -> boolean()).
%%%_* API ==============================================================
-spec new(maybe_lazy_list(T)) -> lazy_list(T).
new(L) when is_list(L) ->
List = fun([H|T]) -> {H, T};
([]) -> fin
end,
new(List, L);
new(Gen) when is_function(Gen, 1) ->
new(Gen, undefined);
new(#lazy_list{} = L) ->
L.
-spec new(lazy_gens:gen(T), term()) -> lazy_list(T).
new(Gen, Acc) ->
#lazy_list{gen=Gen, acc=Acc}.
-spec cons(A, lazy_list(B)) -> lazy_list(A | B).
cons(X, #lazy_list{} = L) ->
append([X], L).
-spec append(maybe_lazy_list(A), maybe_lazy_list(B)) -> lazy_list(A | B).
append(A0, B0) ->
Append = fun(#lazy_list{} = B) ->
case uncons(B) of
empty -> fin;
{Head, Tail} -> {Head, Tail}
end;
({#lazy_list{} = A, #lazy_list{} = B}) ->
case uncons(A) of
empty ->
case uncons(B) of
empty -> fin;
{Head, Tail} -> {Head, {A, Tail}}
end;
{Head, Tail} ->
{Head, {Tail, B}}
end
end,
new(Append, {new(A0), new(B0)}).
-spec zip(maybe_lazy_list(A), maybe_lazy_list(B)) -> lazy_list({A, B}).
zip(A0, B0) ->
Zip = fun({A, B}) ->
case {uncons(A), uncons(B)} of
{empty, _} -> fin;
{_, empty} -> fin;
{{HeadA, TailA}, {HeadB, TailB}} ->
{{HeadA, HeadB}, {TailA, TailB}}
end
end,
new(Zip, {new(A0), new(B0)}).
-spec unzip(lazy_list({A, B})) -> {lazy_list(A), lazy_list(B)}.
unzip(#lazy_list{} = L0) ->
Unzip = fun({Dir, Acc}) ->
case {Dir, uncons(Acc)} of
{left, {{Head, _}, L}} -> {Head, {left, L}};
{right, {{_, Head}, L}} -> {Head, {right, L}};
{_, empty} -> fin
end
end,
{new(Unzip, {left, L0}), new(Unzip, {right, L0})}.
-spec head(lazy_list(T)) -> T.
head(#lazy_list{} = L) ->
{Head, _Tail} = uncons(L),
Head.
-spec tail(lazy_list(T)) -> lazy_list(T).
tail(#lazy_list{} = L) ->
{_Head, Tail} = uncons(L),
Tail.
-spec last(lazy_list(T)) -> T.
last(#lazy_list{} = L) ->
{ok, X} = fold(fun(X, _) -> {ok, X} end, empty, L),
X.
-spec filter(predicate(T), maybe_lazy_list(T)) -> lazy_list(T).
filter(Pred, L0) when is_function(Pred, 1) ->
Filter = fun Filter(L) ->
case uncons(L) of
{Head, Tail} ->
case Pred(Head) of
true -> {Head, Tail};
false -> Filter(Tail)
end;
empty ->
fin
end
end,
new(Filter, new(L0)).
-spec partition(predicate(T), maybe_lazy_list(T)) ->
{lazy_list(T), lazy_list(T)}.
partition(Pred, L0) when is_function(Pred, 1) ->
{filter(Pred, L0), filter(fun(X) -> not Pred(X) end, L0)}.
-spec map(fun((A) -> B), maybe_lazy_list(A)) -> lazy_list(B).
map(Fun, L0) when is_function(Fun, 1) ->
Map = fun(L) ->
case uncons(L) of
{Head, Tail} -> {Fun(Head), Tail};
empty -> fin
end
end,
new(Map, new(L0)).
-spec foreach(fun((T) -> term()), maybe_lazy_list(T)) -> ok.
foreach(Fun, L) ->
fold(fun(X, Acc) -> Fun(X), Acc end, ok, L).
-spec fold(fun((T, Acc) -> Acc), Acc, lazy_list(T)) -> Acc.
fold(Fun, Acc, #lazy_list{} = L0) when is_function(Fun, 2) ->
case uncons(L0) of
{Val, L} -> fold(Fun, Fun(Val, Acc), L);
empty -> Acc
end.
-spec nth_(pos_integer(), lazy_list(T)) -> T.
nth_(N, #lazy_list{} = L) ->
{Val, _} = nth(N, L),
Val.
-spec nth(pos_integer(), lazy_list(T)) -> {T, lazy_list(T)}.
nth(N, #lazy_list{} = L0) when N >= 1 ->
uncons(drop(N-1, L0)).
-spec take(non_neg_integer(), lazy_list(T)) -> {[T], lazy_list(T)}.
take(N, #lazy_list{} = L) when N >= 0 ->
take(N, [], L).
-spec take_(non_neg_integer(), lazy_list(T)) -> [T].
take_(N, #lazy_list{} = L) when N >= 0 ->
{List, _} = take(N, [], L),
List.
-spec take_while(predicate(T), lazy_list(T)) -> {[T], lazy_list(T)}.
take_while(Pred, #lazy_list{} = L) when is_function(Pred, 1) ->
take_while(Pred, [], L).
-spec take_while_(predicate(T), lazy_list(T)) -> [T].
take_while_(Pred, #lazy_list{} = L) when is_function(Pred, 1) ->
{List, _} = take_while(Pred, [], L),
List.
-spec drop(non_neg_integer(), lazy_list(T)) -> lazy_list(T).
drop(0, #lazy_list{} = L) ->
L;
drop(N, #lazy_list{} = L) when N > 0 ->
drop(N-1, tail(L)).
-spec drop_while(predicate(T), lazy_list(T)) -> lazy_list(T).
drop_while(Pred, L) ->
{Head, Tail} = uncons(L),
case Pred(Head) of
true -> drop_while(Pred, Tail);
false -> cons(Head, Tail)
end.
-spec uncons(lazy_list(T)) -> {T, lazy_list(T)} | empty.
uncons(#lazy_list{gen=Gen, acc=Acc0}) ->
case Gen(Acc0) of
{Val, Acc} -> {Val, new(Gen, Acc)};
fin -> empty
end.
-spec limit(non_neg_integer(), lazy_list(T)) -> lazy_list(T).
limit(Len0, #lazy_list{} = L0) ->
MaxLen = fun({0, _}) ->
fin;
({Len, L}) ->
case uncons(L) of
{Head, Tail} -> {Head, {Len-1, Tail}};
empty -> fin
end
end,
new(MaxLen, {Len0, L0}).
-spec while(predicate(T), lazy_list(T)) -> lazy_list(T).
while(Pred, L0) when is_function(Pred, 1) ->
While = fun(L) ->
case uncons(L) of
{Head, Tail} ->
case Pred(Head) of
true -> {Head, Tail};
false -> fin
end;
empty ->
fin
end
end,
new(While, L0).
-spec chunk(pos_integer(), maybe_lazy_list(T)) -> lazy_list([T]).
chunk(N, L0) when N > 1 ->
Chunk = fun(L) ->
case take(N, L) of
{[], _} -> fin;
{Elems, Tail} -> {Elems, Tail}
end
end,
new(Chunk, new(L0)).
-spec every(pos_integer(), lazy_list(T)) -> lazy_list(T).
every(1, #lazy_list{} = L0) ->
L0;
every(Nth, #lazy_list{} = L0) when Nth > 1 ->
Every = fun Every({N, L}) when N rem Nth =:= 0 ->
case uncons(L) of
{Head, Tail} -> {Head, {N+1, Tail}};
empty -> fin
end;
Every({N, L}) ->
case uncons(L) of
{_Head, Tail} ->
Every({N+1, Tail});
empty ->
fin
end
end,
new(Every, {1, L0}).
-spec interleave(maybe_lazy_list(A), maybe_lazy_list(B)) -> lazy_list(A | B).
interleave(A0, B0) ->
Interleave = fun({#lazy_list{} = A, #lazy_list{} = B}) ->
case uncons(A) of
{Head, Tail} -> {Head, {B, Tail}};
empty ->
case uncons(B) of
{Head, Tail} -> {Head, Tail};
empty -> fin
end
end;
(#lazy_list{} = L) ->
case uncons(L) of
{Head, Tail} -> {Head, Tail};
empty -> fin
end
end,
new(Interleave, {new(A0), new(B0)}).
-spec join(A, maybe_lazy_list(B)) -> lazy_list(A | B).
join(X, L0) ->
Join = fun({odd, {Head, Tail}}) ->
{Head, {even, uncons(Tail)}};
({even, {Head, Tail}}) ->
{X, {odd, {Head, Tail}}};
({_, empty}) ->
fin
end,
new(Join, {odd, uncons(new(L0))}).
-spec duplicate(non_neg_integer(), T) -> lazy_list(T).
duplicate(Length, X) ->
limit(Length, duplicate(X)).
-spec duplicate(T) -> lazy_list(T).
duplicate(X) ->
new(fun(Acc) -> {X, Acc} end).
-spec cycle(maybe_lazy_list(T)) -> lazy_list(T).
cycle(L0) ->
Cycle = fun(L) ->
case uncons(L) of
{Head, Tail} ->
{Head, Tail};
empty ->
case uncons(new(L0)) of
{Head, Tail} ->
{Head, Tail};
empty ->
fin
end
end
end,
new(Cycle, new(L0)).
-spec dedup(maybe_lazy_list(T)) -> lazy_list(T).
dedup(L0) ->
Dedup = fun Dedup({Prev, L}) ->
case uncons(L) of
{Prev, Tail} -> Dedup({Prev, Tail});
{Head, Tail} -> {Head, {Head, Tail}};
empty -> fin
end
end,
new(Dedup, {make_ref(), new(L0)}).
-spec expand(lazy_list(T)) -> [T].
expand(#lazy_list{} = L) ->
expand([], L).
-spec sum(lazy_list(number())) -> number().
sum(#lazy_list{} = L) ->
fold(fun(X, Acc) -> X + Acc end, 0, L).
-spec length(lazy_list(number())) -> number().
length(#lazy_list{} = L) ->
fold(fun(_, Acc) -> 1 + Acc end, 0, L).
-spec member(term(), lazy_list()) -> boolean().
member(X, #lazy_list{} = L) ->
case uncons(L) of
{X, _} -> true;
{_, Tail} -> member(X, Tail);
empty -> false
end.
-spec max(lazy_list(T)) -> T.
max(#lazy_list{} = L) ->
{Head, Tail} = uncons(L),
fold(fun(X, Acc) -> max(X, Acc) end, Head, Tail).
-spec min(lazy_list(T)) -> T.
min(#lazy_list{} = L) ->
{Head, Tail} = uncons(L),
fold(fun(X, Acc) -> min(X, Acc) end, Head, Tail).
-spec any(predicate(T), lazy_list(T)) -> boolean().
any(Pred, #lazy_list{} = L) ->
case uncons(L) of
{Head, Tail} ->
case Pred(Head) of
true -> true;
false -> any(Pred, Tail)
end;
empty ->
false
end.
-spec all(predicate(T), lazy_list(T)) -> boolean().
all(Pred, #lazy_list{} = L) ->
case uncons(L) of
{Head, Tail} ->
case Pred(Head) of
true -> all(Pred, Tail);
false -> false
end;
empty ->
true
end.
-spec find(predicate(T), lazy_list(T)) -> {ok, T} | error.
find(Pred, #lazy_list{} = L) ->
case uncons(L) of
{Head, Tail} ->
case Pred(Head) of
true -> {ok, Head};
false -> find(Pred, Tail)
end;
empty ->
error
end.
-spec empty() -> lazy_list().
empty() ->
new([]).
-spec delay(timeout(), lazy_list(T)) -> lazy_list(T).
delay(Ms, L) ->
map(fun(X) -> timer:sleep(Ms), X end, L).
%%%_* Generators -------------------------------------------------------
-spec seq() -> lazy_list(integer()).
seq() ->
new(lazy_gens:seq()).
-spec seq(integer()) -> lazy_list(integer()).
seq(Start) ->
new(lazy_gens:seq(Start)).
-spec seq(integer(), integer()) -> lazy_list(integer()).
seq(Start, Step) ->
new(lazy_gens:seq(Start, Step)).
-spec seq(integer(), integer(), integer()) -> lazy_list(integer()).
seq(Start, Step, End) ->
new(lazy_gens:seq(Start, Step, End)).
-spec rand() -> lazy_list(pos_integer()).
rand() ->
new(lazy_gens:rand()).
-spec rand(pos_integer()) -> lazy_list(pos_integer()).
rand(N) ->
new(lazy_gens:rand(N)).
-spec rand(pos_integer(), rand:state()) -> lazy_list(pos_integer()).
rand(N, State) ->
new(lazy_gens:rand(N, State)).
-spec fib() -> lazy_list(pos_integer()).
fib() ->
new(lazy_gens:fib()).
-spec perms([T]) -> lazy_list([T]).
perms(L) ->
new(lazy_gens:perms(L)).
-spec read_lines(file:name_all()) -> lazy_list([binary()]).
read_lines(Filename) ->
new(lazy_gens:read_lines(Filename)).
%%%_* Internal =========================================================
take(0, Vals, L) ->
{lists:reverse(Vals), L};
take(N, Vals, L0) ->
case uncons(L0) of
{Val, L} -> take(N-1, [Val|Vals], L);
empty -> {lists:reverse(Vals), L0}
end.
take_while(Pred, Vals, L0) ->
case uncons(L0) of
{Val, L} ->
case Pred(Val) of
true -> take_while(Pred, [Val|Vals], L);
false -> {lists:reverse(Vals), L}
end;
empty ->
{lists:reverse(Vals), L0}
end.
expand(Vals, L0) ->
case uncons(L0) of
{Val, L} -> expand([Val|Vals], L);
empty -> lists:reverse(Vals)
end.
%%%_* Tests ============================================================
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
lazy_lists_test() ->
LX = seq(1, 2),
L0 = filter(fun(X) -> X rem 3 =/= 0 end, LX),
L1 = filter(fun(X) -> X rem 5 =/= 0 end, L0),
L = map(fun(7) -> beep;
(X) -> X
end, L1),
{[1, 3, 5, 7, 9], L2} = take(5, LX),
[1, 3, 5, 7, 9] = take_(5, LX),
[1, 3, 5, 7, 9] = expand(limit(5, LX)),
{[11, 13, 15, 17, 19], _} = take(5, L2),
{[1, beep, 11, 13, 17], L3} = take(5, L),
{[19, 23, 29, 31, 37], _} = take(5, L3),
ok.
init_list_test() ->
L = new([1,2,3]),
[1,2,3] = expand(L).
fold_test() ->
L = new([1,2,3]),
6 = fold(fun(V, Acc) -> Acc + V end, 0, L).
take_while_test() ->
L = seq(),
{[1,2,3], L2} = take_while(fun(X) -> X < 4 end, L),
{[], _} = take_while(fun(X) -> X < 4 end, L2).
fizzbuzz_test() ->
FizzBuzz = fun(X) when X rem 3 =:= 0, X rem 5 =:= 0 -> fizzbuzz;
(X) when X rem 3 =:= 0 -> fizz;
(X) when X rem 5 =:= 0 -> buzz;
(X) -> X
end,
L0 = map(FizzBuzz, seq()),
fizz = nth_(9, L0),
14 = nth_(14, L0),
fizzbuzz = nth_(15, L0).
append_test() ->
[1,2,3,4,5,6] = expand(append([1,2,3], [4,5,6])).
duplicate_test() ->
[a,a,a,a,a,a,a] = take_(7, duplicate(a)),
[a,a,a,a,a,a,a] = expand(duplicate(7, a)).
any_test() ->
true = any(fun(X) -> X > 8 end, seq()),
false = any(fun(X) -> X > 8 end, limit(8, seq())).
all_test() ->
true = all(fun(X) -> X < 9 end, limit(8, seq())),
false = all(fun(X) -> X < 8 end, limit(8, seq())).
cycle_test() ->
[a,b,c,a,b,c,a] = take_(7, cycle([a,b,c])).
cons_test() ->
L = seq(),
{1, L2} = uncons(L),
[1,2,3] = take_(3, L),
[1,2,3] = take_(3, cons(1, L2)),
[1,2,3] = expand(cons(1, cons(2, cons(3, empty())))).
seq_test() ->
L = seq(1,1,3),
[1,2,3] = expand(L).
interleave_test() ->
[] = expand(interleave([], [])),
[1,a,2,b,3,c] = expand(interleave([1,2,3], [a,b,c])),
[1,a,2,b,3,c,d,e,f] = expand(interleave([1,2,3], [a,b,c,d,e,f])),
[1,a,2,b,3,c,4,5,6] = expand(interleave([1,2,3,4,5,6], [a,b,c])).
join_test() ->
[] = expand(join(x, [])),
[1,x,2,x,3] = expand(join(x, seq(1,1,3))).
perms_test() ->
[[]] = expand(perms([])),
[[1]] = expand(perms([1])),
[[1,2], [2,1]] = expand(perms([1,2])),
[[1,2,3], [1,3,2], [3,1,2], [3,2,1], [2,3,1], [2,1,3]] =
expand(perms([2,3,1])),
ok.
find_test() ->
{ok, 4} = find(fun(X) -> X > 3 end, seq()),
error = find(fun(X) -> X > 3 end, seq(1,1,3)).
member_test() ->
false = member(4, empty()),
true = member(4, seq()),
false = member(4, seq(1,1,3)).
partition_test() ->
{A, B} = partition(fun(X) -> X rem 2 =:= 0 end, seq()),
[2,4,6] = take_(3, A),
[1,3,5] = take_(3, B).
length_test() ->
0 = ?MODULE:length(empty()),
3 = ?MODULE:length(new([1,2,3])).
zip_test() ->
L = zip([a,b,c], seq()),
[{a,1}, {b,2}, {c,3}] = expand(L),
{A, B} = unzip(L),
[a,b,c] = expand(A),
[1,2,3] = expand(B).
fib_test() ->
[1,1,2,3,5,8,13] = take_(7, fib()).
head_test() ->
?assertError(_, head(empty())),
1 = head(seq()).
last_test() ->
?assertError(_, last(empty())),
3 = last(new([1,2,3])).
-endif.