Erlang Language

Loop and Recursion

Syntax#

  • function ( list | iolist | tuple ) -> function( tail ).

Remarks#

Why recursive functions?

Erlang is a functional programming language and don’t any kind of loop structure. Everything in functional programming is based on data, type and functions. If you want a loop, you need to create a function who call itself.

Traditional while or for loop in imperative and object oriented language can be represented like that in Erlang:

loop() ->
  % do something here
  loop().

Good method to understand this concept is to expand all function calls. We’ll see that on other examples.

List

Here the simplest recursive function over list type. This function only navigate into a list from its start to its end and do nothing more.

-spec loop(list()) -> ok.
loop([]) ->
  ok;
loop([H|T]) ->
  loop(T).

You can call it like this:

loop([1,2,3]). % will return ok.

Here the recursive function expansion:

loop([1|2,3]) ->
  loop([2|3]) ->
    loop([3|]) ->
      loop([]) ->
        ok.

Recursive loop with IO actions

Previous code do nothing, and is pretty useless. So, we will create now a recursive function who execute some actions. This code is similar to lists:foreach/2.

-spec loop(list(), fun()) -> ok.
loop([], _) ->
  ok;
loop([H|T], Fun) 
  when is_function(Fun) ->
    Fun(H),
    loop(T, Fun).

You can call it in like this:

Fun = fun(X) -> io:format("~p", [X]) end.
loop([1,2,3]).

Here the recursive function expansion:

loop([1|2,3], Fun(1)) ->
  loop([2|3], Fun(2)) ->
    loop([3|], Fun(3)) ->
      loop([], _) ->
        ok.

You can compare with lists:foreach/2 output:

lists:foreach(Fun, [1,2,3]).

Recursive loop over list returning modified list

Another useful example, similar to lists:map/2. This function will take one list and one anonymous function. Each time a value in list is matched, we apply function on it.

-spec loop(A :: list(), fun()) -> list().
loop(List, Fun) 
  when is_list(List), is_function(Fun) ->
    loop(List, Fun, []).

-spec loop(list(), fun(), list()) -> list() | {error, list()}.
loop([], _, Buffer) 
  when is_list(Buffer) ->
    lists:reverse(Buffer);
loop([H|T], Fun, Buffer) 
  when is_function(Fun), is_list(Buffer) ->
    BufferReturn = [Fun(H)] ++ Buffer,
    loop(T, Fun, BufferReturn).

You can call it like this:

Fun(X) -> X+1 end.
loop([1,2,3], Fun).

Here the recursive function expansion:

loop([1|2,3], Fun(1), [2]) ->
  loop([2|3], Fun(2), [3,2]) ->
    loop([3|], Fun(3), [4,3,2]) ->
      loop([], _, [4,3,2]) ->
        list:reverse([4,3,2]) ->
          [2,3,4].

This function is also called “tail recursive function”, because we use a variable like an accumulator to pass modified data over multiple execution context.

Iolist and Bitstring

Like list, simplest function over iolist and bitstring is:

-spec loop(iolist()) -> ok | {ok, iolist} .
loop(<<>>) ->
  ok;
loop(<<Head, Tail/bitstring>>) ->
  loop(Tail);
loop(<<Rest/bitstring>>) ->
  {ok, Rest}

You can call it like this:

loop(<<"abc">>).

Here the recursive function expansion:

loop(<<"a"/bitstring, "bc"/bitstring>>) ->
  loop(<<"b"/bitstring, "c"/bitstring>>) ->
    loop(<<"c"/bitstring>>) ->
      loop(<<>>) ->
        ok.

Recursive function over variable binary size

This code take bitstring and dynamically define binary size of it. So if, if we set a size of 4, every 4 bits, a data will be matched. This loop do nothing interesting, its just our pillar.

loop(Bitstring, Size) 
  when is_bitstring(Bitstring), is_integer(Size) ->
    case Bitstring of
      <<>> -> 
        ok;
      <<Head:Size/bitstring,Tail/bitstring>> ->
        loop(Tail, Size);
      <<Rest/bitstring>> ->
        {ok, Rest}
    end.

You can call it like this:

loop(<<"abc">>, 4).

Here the recursive function expansion:

loop(<<6:4/bitstring, 22, 38, 3:4>>, 4) ->
  loop(<<1:4/bitstring, "bc">>, 4) ->
    loop(<<6:4/bitstring, 38,3:4>>, 4) ->
      loop(<<2:4/bitstring, "c">>, 4) ->
        loop(<<6:4/bitstring, 3:4>>, 4) ->
          loop(<<3:4/bitstring>>, 4) ->
            loop(<<>>, 4) ->
              ok.

Our bitstring is splitted over 7 patterns. Why? Because by default, Erlang use binary size of 8 bits, if we split it in two, we have 4 bits. Our string is 8*3=24 bits. 24/4=6 patterns. Last pattern is <<>>. loop/2 function is called 7 times.

recursive function over variable binary size with actions

Now, we can do more interesting thing. This function take one more argument, an anonymous function. Everytime we match a pattern, this one will be passed to it.

-spec loop(iolist(), integer(), function()) -> ok.
loop(Bitstring, Size, Fun) ->
  when is_bitstring(Bitstring), is_integer(Size), is_function(Fun) ->
        case Bitstring of
      <<>> -> 
        ok;
      <<Head:Size/bitstring,Tail/bitstring>> ->
        Fun(Head),
        loop(Tail, Size, Fun);
      <<Rest/bitstring>> ->
        Fun(Rest),
        {ok, Rest}
    end.

You can call it like this:

Fun = fun(X) -> io:format("~p~n", [X]) end.
loop(<<"abc">>, 4, Fun).

Here the recursive function expansion:

loop(<<6:4/bitstring, 22, 38, 3:4>>, 4, Fun(<<6:4>>) ->
  loop(<<1:4/bitstring, "bc">>, 4, Fun(<<1:4>>)) ->
    loop(<<6:4/bitstring, 38,3:4>>, 4, Fun(<<6:4>>)) ->
      loop(<<2:4/bitstring, "c">>, 4, Fun(<<2:4>>)) ->
        loop(<<6:4/bitstring, 3:4>>, 4, Fun(<<6:4>>) ->
          loop(<<3:4/bitstring>>, 4, Fun(<<3:4>>) ->
            loop(<<>>, 4) ->
              ok.

Recursive function over bitstring returning modified bitstring

This one is similar to lists:map/2 but for bitstring and iolist.

% public function (interface).
-spec loop(iolist(), fun()) -> iolist() | {iolist(), iolist()}.
loop(Bitstring, Fun) ->
  loop(Bitstring, 8, Fun).

% public function (interface).
-spec loop(iolist(), integer(), fun()) -> iolist() | {iolist(), iolist()}.
loop(Bitstring, Size, Fun) ->
  loop(Bitstring, Size, Fun, <<>>)

% private function.
-spec loop(iolist(), integer(), fun(), iolist()) -> iolist() | {iolist(), iolist()}.
loop(<<>>, _, _, Buffer) ->
  Buffer;
loop(Bitstring, Size, Fun, Buffer) ->
  when is_bitstring(Bitstring), is_integer(Size), is_function(Fun) ->
    case Bitstring of
      <<>> -> 
        Buffer;
      <<Head:Size/bitstring,Tail/bitstring>> ->
        Data = Fun(Head),
        BufferReturn = <<Buffer/bitstring, Data/bitstring>>,
        loop(Tail, Size, Fun, BufferReturn);
      <<Rest/bitstring>> ->
        {Buffer, Rest}
    end.

This code seems more complexe. Two functions were added: loop/2 and loop/3. These two functions are simple interface to loop/4.

You can execute it like this:

Fun = fun(<<X>>) -> << (X+1) >> end.
loop(<<"abc">>, Fun).
% will return <<"bcd">>

Fun = fun(<<X:4>>) -> << (X+1) >> end.
loop(<<"abc">>, 4, Fun).
% will return <<7,2,7,3,7,4>>

loop(<<"abc">>, 4, Fun, <<>>).
% will return <<7,2,7,3,7,4>>

Map

Map in Erlang is equivalent of hashes in Perl or dictionaries in Python, its a key/value store. To list every value stored in, you can list every key, and return key/value pair. This first loop give you an idea:

loop(Map) when is_map(Map) -> 
  Keys = maps:keys(Map),
  loop(Map, Keys).

loop(_ , []) ->
  ok;
loop(Map, [Head|Tail]) ->
  Value = maps:get(Head, Map),
  io:format("~p: ~p~n", [Head, Value]),
  loop(Map, Tail).

You can execute it like that:

Map = #{1 => "one", 2 => "two", 3 => "three"}.
loop(Map).
% will return:
% 1: "one"
% 2: "two"
% 3: "three"

Managing State

Recursive function use their states to loop. When you spawn new process, this process will be simply a loop with some defined state.

Anonymous function

Here 2 examples of recursive anonymous functions based on previous example. Firstly, simple infinite loop:

InfiniteLoop = fun 
  R() -> 
    R() end.

Secondly, anonymous function doing loop over list:

LoopOverList = fun 
  R([]) -> ok;
  R([H|T]) ->
    R(T) end.

These two functions could be rewritten as:

InfiniteLoop = fun loop/0.

In this case, loop/0 is a reference to loop/0 from remarks. Secondly, with little more complex:

LoopOverLlist = fun loop/2.

Here, loop/2 is a reference to loop/2 from list example. These two notations are syntactic sugar.


This modified text is an extract of the original Stack Overflow Documentation created by the contributors and released under CC BY-SA 3.0 This website is not affiliated with Stack Overflow