Range Over Iterators in Erlang

Starting with version 1.23, the language has added support for iterators, which lets us iterate over pretty much anything!

Let’s look at the List type from the previous example again. In that example, we had an AllElements method that returned a slice of all elements in the list. With iterators, we can do it better - as shown below.

-module(list_iter).
-export([main/0, push/2, all/1, gen_fib/0]).

-record(list, {head=nil, tail=nil}).
-record(element, {next=nil, val}).

push(V, #list{head=nil} = L) ->
    E = #element{val=V},
    L#list{head=E, tail=E};
push(V, #list{tail=T} = L) ->
    E = #element{val=V},
    T#element{next=E},
    L#list{tail=E}.

all(#list{head=H}) ->
    fun(Yield) -> elements(Yield, H) end.

elements(_, nil) -> ok;
elements(Yield, #element{val=V, next=N}) ->
    case Yield(V) of
        false -> ok;
        true -> elements(Yield, N)
    end.

gen_fib() ->
    fun(Yield) ->
        gen_fib_internal(Yield, 1, 1)
    end.

gen_fib_internal(Yield, A, B) ->
    case Yield(A) of
        false -> ok;
        true -> gen_fib_internal(Yield, B, A + B)
    end.

main() ->
    L = #list{},
    L1 = push(10, L),
    L2 = push(13, L1),
    L3 = push(23, L2),
    Env = fun(E) -> io:format("~p~n", [E]), true end,
    lists:foreach(Env, collect(all(L3))),
    EnvFib = fun(N) when N >= 10 -> false; (N) -> io:format("~p~n", [N]), true end,
    lists:foreach(EnvFib, collect(gen_fib())).

collect(Iterator) ->
    Result = [],
    Iterator(fun(E) -> Result = [E | Result], true end),
    lists:reverse(Result).

all/1 returns an iterator, which is a function taking another function as a parameter, conventionally called Yield. It will call Yield for every element we want to iterate over, and note Yield’s return value for potential early termination.

Iteration doesn’t require an underlying data structure, and doesn’t even have to be finite! Here’s a function returning an iterator over Fibonacci numbers: it keeps running as long as Yield keeps returning true.

Since all/1 returns an iterator, we can use it in a regular iteration loop.

Packages like lists have several useful functions to work with iterators. For example, collect/1 takes any iterator and collects all its values into a list.

Once the loop hits break or an early return, the Yield function passed to the iterator will return false.