Streams/lazy lists

Richard Carlsson richardc@REDACTED
Wed Oct 18 16:55:28 CEST 2000


Well, if anyone is still keen on playing around with these things, I
edited the implementation a bit: now called "streams" and uses tuples
instead of cons cells. Enjoy.

	/Richard

Richard Carlsson (richardc@REDACTED)   (This space intentionally left blank.)
E-mail: Richard.Carlsson@REDACTED	WWW: http://www.csd.uu.se/~richardc/
-------------- next part --------------
%% ===============================================================
%% Lazy streams
%%
%% Copyright (C) 2000 Richard Carlsson
%%
%% This library is free software; you can redistribute it and/or
%% modify it under the terms of the GNU Lesser General Public
%% License as published by the Free Software Foundation; either
%% version 2 of the License, or (at your option) any later
%% version.
%%
%% This library is distributed in the hope that it will be useful,
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
%% GNU Lesser General Public License for more details.
%%
%% You should have received a copy of the GNU Lesser General
%% Public License along with this library; if not, write to the
%% Free Software Foundation, Inc., 59 Temple Place, Suite 330,
%% Boston, MA 02111-1307 USA
%%
%% Author contact: richardc@REDACTED
%% ===============================================================

-module(streams).

-vsn('$Id: streams.erl,v 1.2 2000/10/18 13:58:07 richardc Exp $').

-author('richardc@REDACTED').

-copyright('Copyright (C) 2000 Richard Carlsson').

-export([append/2, constant/1, drop/2, duplicate/2, empty/0,
	 filter/2, first/2, foldl/3, foldr/3, foreach/2,
	 integers/1, map/2, member/2, merge/3, nth/2, nthtail/2,
	 push/2, random/0, random_integers/1, seq/2, seq/3,
	 stream/1, subsequence/3, to_list/1]).

%% The type of the streams defined below is:
%%
%%	Stream :: () -> {} | {term(), Stream}
%%
%% A stream is thus always a fun. One advantage is that if a
%% producer has side effects (e.g., if it reads elements from a
%% file), we need never trigger the next effect unless we really
%% want to see the next element. Also, programming becomes very
%% straightforward. Let's hope for better compilers to make this
%% sort of programming more efficient.


%% ===============================================================
%% The following functions compute new streams without forcing
%% evaluation of their input.


%% The empty stream

empty() -> fun () -> {} end.


%% The stream consisting of H followed by all elements of T.

push(H, T) -> fun () -> {H, T} end.


%% Converts a stream into a list. Make sure never to pass an
%% infinite stream to this function!

to_list(S) ->
    case S() of
	{H, T} ->
	    [H | to_list(T)];    
	{} ->
	    []
    end.


%% Turns a proper normal list into a stream.

stream(L) ->
    fun () ->
	    case L of
		[H | T] ->
		    {H, stream(T)};
		[] ->
		    {}
	    end
    end.


%% The stream of all elements of S1 followed by all elements of
%% S2.

append(S1, S2) ->
    fun () ->
	    case S1() of
		{} ->
		    S2(); 
		{H, T} ->
		    {H, append(T, S2)}
	    end
    end.


%% The stream of integers in the interval [From, To], in ascending
%% order.

seq(From, To) ->
    seq(From, To, 1).

%% The stream of integers [From, From + D, From + 2*D, ..., From +
%% D*((To - From) mod D)]. The interval is empty if D does not
%% have the same sign as the difference To - From.

seq(From, To, D) when integer(From), integer(To),
		      From < To, D > 0 ->
    fun () -> {From, seq(From + D, To, D)} end;
seq(From, To, D) when integer(From), integer(To),
		      To < From, D < 0 ->
    fun () -> {From, seq(From + D, To, D)} end;
seq(From, To, D) when integer(From), integer(To), To == From ->
    fun () -> {From, empty()} end;
seq(From, To, D) ->
    empty().


%% The stream of integers starting at N.

integers(N) ->
    fun () -> {N, integers(N + 1)} end.


%% The infinite stream of elements X.

constant(X) ->
    fun () -> {X, constant(X)} end.


%% The stream of length N, where each element is X.

duplicate(N, X) when integer(N) ->
    fun () ->
	    if N > 0 ->
		    {X, duplicate(N - 1, X)};
	       N == 0 ->
		    {}
	    end
    end.


%% An infinite stream of random floating-point numbers in the
%% interval [0.0, 1.0]. `random:seed' must be used (by the same
%% process) to set the seed for the random number generator before
%% this function is called.

random() ->
    fun () -> {random:uniform(), random()} end.

%% An infinite stream of random integers in the interval [0, Max].
%% See `random' above for details.

random_integers(Max) when Max >= 1 ->
    fun () -> {random:uniform(Max), random_integers(Max)} end.


%% The stream consisting of the first N elements of S, or the
%% stream S if the length of S is not greater than N.

first(0, S) -> empty();
first(N, S) when integer(N), N > 0 ->
    fun () ->
	    case S() of
		{H, T} ->
		    {H, first(N - 1, T)};
		{} ->
		    {}
	    end
    end.

%% The stream [EN+1, EN+2, ...], if S is [E1, ..., EN, EN+1, ...].

drop(0, S) -> S;
drop(N, S) when integer(N), N > 0 ->
    fun () ->
	    case S() of
		{H, T} ->
		    (drop(N - 1, T))();
		{} ->
		    {}
	    end
    end.

%% The stream [EN, EN+1, ..., EN+D], if S is [E1, E2, ...].

subsequence(N, D, S) when N > 0, D >= 0 ->
    first(D, drop(N, S)).

%% The stream [F(E1), F(E2), F(E3), ...] if S is [E1, E2, E3,
%% ...].

map(F, S) ->
    fun () ->
	    case S() of
		{H, T} ->
		    {F(H), map(F, T)};
		{} ->
		    {}
	    end
    end.


%% The stream of all elements E in S (in the same order) for which
%% P(E) returns `true'. P must return either `true' or `false' for
%% all elements in S.

filter(P, S) ->
    fun () ->
	    case S() of
		{H, T} ->
		    case P(H) of
			true ->
			    {H, filter(P, T)};
			false ->
			    (filter(P, T))()
		    end;
		{} ->
		    {}
	    end
    end.


%% Returns the stream of elements in S1 and S2 where the
%% respective relative order of elements is preserved, and each
%% element Y of S2 is ordered before the first possible X of S1
%% such that P(X, Y) yields `false'. P(X, Y) must yield either
%% `true' or `false' for all X in S1 and Y in S2. (P can be read
%% as "less than".)

merge(P, S1, S2) ->
    fun () ->
	    case S1() of
		{H1, T1} ->
		    case S2() of
			{H2, T2} ->
			    case P(H1, H2) of
				true ->
				    {H1, merge(P, T1,
					       push(H2, T2))}; 
				false ->
				    {H2, merge(P, push(H1, T1),
					       T2)}
			    end;
			{} ->
			    {H1, T1}
		    end;
		{} ->
		    S2()
	    end
    end.


%% ===============================================================
%% All functions below may evaluate all or part of their input.


%% Returns `true' if X is in the stream S, and `false' otherwise.

member(X, S) ->
    case S() of
	{H, T} ->
	    if H == X -> true;
	       true -> member(X, T)
	    end;
	{} -> false
    end.


%% Returns the Nth element of stream S.

nth(N, S) ->
    S1 = S(),
    if N > 1 ->
	    nth(N - 1, tl(S1));
       true ->
	    hd(S1)
    end.


%% Returns the stream [AN+1, AN+2, ...] if S is [A1, A2, ..., AN,
%% AN+1, AN+2, ...].

nthtail(N, S) when N > 1 ->
    nthtail(N - 1, tl(S()));
nthtail(0, S) ->
    S.


%% Computes (...((A F E1) F E2)... F EN), if S is [E1, E2, ...,
%% EN] and F is a binary function (here written as an infix
%% operator).

foldl(A, F, S) ->
    case S() of
	{H, T} ->
	    foldl(F(A, H), F, T);
	{} ->
	    A
    end.


%% Computes (E1 F ...(EN-1 F (EN F A))...), if S is [E1, E2, ...,
%% EN] and F is a binary function (here written as an infix
%% operator).

foldr(S, F, A) ->
    case S() of
	{H, T} ->
	    F(H, foldr(T, F, A));
	{} ->
	    A
    end.


%% Evaluates F(E1), F(E2), ..., if S is [E1, E2, ...]. Always
%% returns `ok'.

foreach(F, S) ->
    case S() of
	{H, T} ->
	    F(H),
	    foreach(F, T);
	{} ->
	    ok
    end.


%% ===============================================================


More information about the erlang-questions mailing list