This blog has moved here and this post can be found at http://blog.khigia.net/wp-import/erlang/2006/11/25/baby-steps-in-the-erlang-world.html
My projects are all suspended … I have been busy playing with stream examples of chapter 3 of SICP
.
I wrote in Erlang a simple implementation of stream as cons-list. It is not meant to be efficient but easy to understand (and debug):
- a stream is here a tuple {stream, Head, DelayedTail}, where DelayedTail is a 0-arity’s function which return the next stream element tuple;
- a macro implements the delay function used to construct a stream tail:
-define(DELAY(Any), fun() -> Any end).
Note: the head is not delayed, and if we need a delayed head this probably means that we need a lazy-evaluation model, to which I’ll come later. Using abstract functions, head/1 to access current value and tail/1 to force evaluation of next stream state, will enables to hook a memoization process if needed.
Anyway, the stream concatenation and the delay’s macro are enough to build all stream manipulations. Here are some examples:
% map: application of a procedure to all elements of a stream
map(Proc, Stream={stream, undefined, undefined}) ->
% empty stream: do nothing
Stream;
map(Proc, Stream) ->
% apply Proc to current, and delay apply to all tail elements
{stream, Proc(head(Stream)), ?DELAY(map(Proc, tail(Stream)))}
% filter elements of stream with respect to some predicate
filter(Pred, Stream={stream, undefined, undefined}) ->
% empty stream: do nothing
Stream;
filter(Pred, Stream={stream, _Head, _Tail}) ->
Current = head(Stream),
case Pred(Current) of
true ->
{stream, Current, ?DELAY(filter(Pred, tail(Stream)))};
_False ->
filter(Pred, tail(Stream))
end.
% generalized map: apply proc to multiple streams (traverse streams "in parallel")
gmap(Proc, Streams=[First={stream, undefined,undefined}|_Others]) ->
% assume all stream have the same length
% found one stream empty: end of process
First;
gmap(Proc, Streams=[First|_Others]) ->
% create a list of heads
Heads = lists:map(fun(S) -> stream:head(S) end, Streams),
% create a generator of the list of tails
FTails = fun() -> lists:map(fun(S) -> stream:tail(S) end, Streams) end,
% result is the application to heads and delayed mapping to tails
{stream, Proc(Heads), ?DELAY(gmap(Proc, FTails()))}.
And now, we have enough to use the streams:
% explicit definition of infinite sequence of integers
explicit_integers_from(N) ->
{stream, N, ?DELAY(explicit_integers_from(N+1))}.
% add values of 2 streams
add(S1, S2) ->
gmap(fun([X,Y]) -> X + Y end, [S1, S2]).
% infinite sequence of Fibonacci numbers
fibs() ->
{stream, 0, ?DELAY({stream, 1, ?DELAY(add(fibs(), tail(fibs())))}) }.
A bit more usefull: 2 infinite sequences of prime numbers. First one uses the sieve of Eratosthene; second one uses basic definition of prime: number not divisible by any previous primes.
% Sieve of Eratosthene:
sieve(Stream) ->
Current = head(Stream),
{stream, Current, ?DELAY(sieve(filter(
fun(N) -> N rem Current /= 0 end,
tail(Stream)
)))}.
sieve_primes() ->
sieve(explicit_integers_from(2)).
% prime numbers using the factors decomposition property
primes() ->
{stream, 2, ?DELAY(filter(fun is_prime/1, explicit_integers_from(3)))}.
is_prime(N) ->
iter_is_prime(N, primes()).
iter_is_prime(N, Primes) ->
FirstPrime = head(Primes),
if
FirstPrime * FirstPrime > N ->
true;
N rem FirstPrime == 0 ->
false;
true ->
iter_is_prime(N, tail(Primes))
end.
And of course there are lot of stream examples in SICP chapter, like the impressive Euler transformation to accelerate convergence of sequence.
In this thread http://erlang.mirror.su.se/ml-archive/erlang-questions/200010/msg00165.html you can find a real stream implementation by Richard Carlsson, as well as interesting discussions about stream and lazy evaluation. Also check this other thread http://www.erlang.org/ml-archive/erlang-questions/200602/msg00003.html if you’re interested by memoization in Erlang. I may test it later to learn the magic of Erlang parse_transform
, but I think an easy possible way to implement memoization for my stream is to model a stream as a tuple of 4 elements, adding a dictionary of memoized values as last element (my current implementation uses the process dictionary to store memoized values).
Actually I’m not sure if this post is too short to be a tutorial or too long to be a interesting post! Anyway, it exists now
… mostly because a friend gave me motivation to do so, thanks!
May 30, 2007 at 1:41 pm
It’s too bad that fun() instantiation and invocation is slow. I wrote the stream stuff in Javascript for giggles, and it suffers a lot from that too.
I keep thinking that there should be a good way to do this in Erlang using Erlang processes, but I haven’t thought of any “clean” way yet. (One problem is cleaning up.)
July 27, 2007 at 4:07 am
[...] under erlang , Programming I didn’t known this was a so popular problem when I coded a sieve of Eratosthene using stream design from [...]
June 11, 2008 at 3:35 pm
[...] OCaml code use a stream implementation (lazy list) … (mostly equivalent to my previous Erlang [...]
February 1, 2011 at 7:01 pm
Excellent website you have here but I was wanting to know if you knew of any discussion boards that cover the same topics talked about in this article? I’d really love to be a part of online community where I can get suggestions from other experienced people that share the same interest. If you have any recommendations, please let me know. Thanks!