Чтобы понять, почему накопление и обращение происходит достаточно быстро, вы должны понять, как создаются списки в Erlang. Списки Erlangs, подобные спискам в Lisp, построены из cons-ячеек (посмотрите на картинку в ссылке).
В односвязном списке, таком как списки Эрланга, очень дешево добавить элемент (или короткий список). Это была конструкция List = [H|T]
.
Перевернуть односвязный список, состоящий из cons-ячеек, довольно быстро, поскольку вам нужно всего лишь один проход по списку, просто добавив следующий элемент к вам уже полностью измененный частичный результат. И, как уже упоминалось, он также реализован на C в Erlang.
Кроме того, построение результата в обратном порядке часто может быть выполнено с помощью хвостовой рекурсивной функции, которая означает, что стек не создается и ( только в старых версиях Erlang! ), поэтому некоторая память может быть сохранена.
Сказав все это: это один из Восьми мифов о производительности Эрланга , который всегда лучше встроить обратно в хвостовую рекурсивную функцию и вызвать lists:reverse/1
в конце.
Вот рекурсивная версия тела без lists:reverse/1
, которая использовала бы больше памяти в версиях Erlang до 12, но не в текущих версиях:
map([H|T], Fun) ->
[ Fun(H) | map(T,Fun) ];
map([],_) -> [].
А вот версия карты с использованием списочных представлений:
map(List, Fun) ->
[ Fun(X) || X <- List ].
Как вы можете видеть, это так просто, потому что map
- это просто встроенная часть понимания списка, поэтому вы могли бы использовать понимание списка напрямую и больше не нуждаться в map
.
В качестве дополнительной чистой реализации Erlang, которая показывает, как эффективно работает обращение списка cons-ячеек (в Erlang всегда быстрее вызывать lists:reverse/1
, потому что он в C, но делает то же самое).
reverse(List) ->
reverse(List, []).
reverse([H|T], Acc) ->
reverse(T, [H|Acc]);
reverse([], Acc) ->
Acc.
Как вы можете видеть, в списке есть только [A|B]
операций, разбирая минусы (при сопоставлении с образцом) и создавая новые при выполнении [H|Acc]
.