View Source List Handling
Creating a List
Lists can only be built starting from the end and attaching list elements at the
beginning. If you use the ++
operator as follows, a new list is created that
is a copy of the elements in List1
, followed by List2
:
List1 ++ List2
Looking at how lists:append/2
or ++
would be implemented in plain Erlang,
clearly the first list is copied:
append([H|T], Tail) ->
[H|append(T, Tail)];
append([], Tail) ->
Tail.
When recursing and building a list, it is important to ensure that you attach the new elements to the beginning of the list. In this way, you will build one list, not hundreds or thousands of copies of the growing result list.
Let us first see how it is not to be done:
DO NOT
bad_fib(N) ->
bad_fib(N, 0, 1, []).
bad_fib(0, _Current, _Next, Fibs) ->
Fibs;
bad_fib(N, Current, Next, Fibs) ->
bad_fib(N - 1, Next, Current + Next, Fibs ++ [Current]).
Here more than one list is built. In each iteration step a new list is created that is one element longer than the new previous list.
To avoid copying the result in each iteration, build the list in reverse order and reverse the list when you are done:
DO
tail_recursive_fib(N) ->
tail_recursive_fib(N, 0, 1, []).
tail_recursive_fib(0, _Current, _Next, Fibs) ->
lists:reverse(Fibs);
tail_recursive_fib(N, Current, Next, Fibs) ->
tail_recursive_fib(N - 1, Next, Current + Next, [Current|Fibs]).
List Comprehensions
A list comprehension:
[Expr(E) || E <- List]
is basically translated to a local function:
'lc^0'([E|Tail], Expr) ->
[Expr(E)|'lc^0'(Tail, Expr)];
'lc^0'([], _Expr) -> [].
If the result of the list comprehension will obviously not be used, a list will not be constructed. For example, in this code:
[io:put_chars(E) || E <- List],
ok.
or in this code:
case Var of
... ->
[io:put_chars(E) || E <- List];
... ->
end,
some_function(...),
the value is not assigned to a variable, not passed to another function, and not returned. This means that there is no need to construct a list and the compiler will simplify the code for the list comprehension to:
'lc^0'([E|Tail], Expr) ->
Expr(E),
'lc^0'(Tail, Expr);
'lc^0'([], _Expr) -> [].
The compiler also understands that assigning to _
means that the value will
not be used. Therefore, the code in the following example will also be optimized:
_ = [io:put_chars(E) || E <- List],
ok.
Deep and Flat Lists
lists:flatten/1
builds an entirely new list. It is therefore expensive, and
even more expensive than the ++
operator (which copies its left argument,
but not its right argument).
In the following situations it is unnecessary to call lists:flatten/1
:
- When sending data to a port. Ports understand deep lists so there is no reason to flatten the list before sending it to the port.
- When calling BIFs that accept deep lists, such as
list_to_binary/1
oriolist_to_binary/1
. - When you know that your list is only one level deep. Use
lists:append/1
instead.
Examples:
DO
port_command(Port, DeepList)
DO NOT
port_command(Port, lists:flatten(DeepList))
A common way to send a zero-terminated string to a port is the following:
DO NOT
TerminatedStr = String ++ [0],
port_command(Port, TerminatedStr)
Instead:
DO
TerminatedStr = [String, 0],
port_command(Port, TerminatedStr)
DO
1> lists:append([[1], [2], [3]]).
[1,2,3]
DO NOT
1> lists:flatten([[1], [2], [3]]).
[1,2,3]
Recursive List Functions
There are two basic ways to write a function that traverses a list and produces a new list.
The first way is writing a body-recursive function:
%% Add 42 to each integer in the list.
add_42_body([H|T]) ->
[H + 42 | add_42_body(T)];
add_42_body([]) ->
[].
The second way is writing a tail-recursive function:
%% Add 42 to each integer in the list.
add_42_tail(List) ->
add_42_tail(List, []).
add_42_tail([H|T], Acc) ->
add_42_tail(T, [H + 42 | Acc]);
add_42_tail([], Acc) ->
lists:reverse(Acc).
In early version of Erlang the tail-recursive function would typically be more efficient. In modern versions of Erlang, there is usually not much difference in performance between a body-recursive list function and tail-recursive function that reverses the list at the end. Therefore, concentrate on writing beautiful code and forget about the performance of your list functions. In the time-critical parts of your code, measure before rewriting your code.
For a thorough discussion about tail and body recursion, see Erlang's Tail Recursion is Not a Silver Bullet.
Note
This section is about list functions that construct lists. A tail-recursive function that does not construct a list runs in constant space, while the corresponding body-recursive function uses stack space proportional to the length of the list.
For example, a function that sums a list of integers, is not to be written as follows:
DO NOT
recursive_sum([H|T]) -> H+recursive_sum(T);
recursive_sum([]) -> 0.
Instead:
DO
sum(L) -> sum(L, 0).
sum([H|T], Sum) -> sum(T, Sum + H);
sum([], Sum) -> Sum.