Lists can only be built starting from the end and attaching list
elements at the beginning. If you use the "
List1 ++ List2
Looking at how
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, 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, 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]).]]>
Lists comprehensions still have a reputation for being slow. They used to be implemented using funs, which used to be slow.
A list comprehension:
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:
or in this code:
[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 used. Therefore, the code in the following example will also be optimized:
In the following situations, you can easily avoid calling
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], % String="foo" => [$f, $o, $o, 0] port_command(Port, TerminatedStr) ...
Instead:
DO
... TerminatedStr = [String, 0], % String="foo" => [[$f, $o, $o], 0] port_command(Port, TerminatedStr) ...
DO
> lists:append([[1], [2], [3]]). [1,2,3] >
DO NOT
> lists:flatten([[1], [2], [3]]). [1,2,3] >
In section about myths, the following myth was exposed:
There is usually not much difference 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 (and only there), measure before rewriting your code.
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.