Поскольку OP изучает Erlang, обратите внимание также, что функция list_length/1
не поддается оптимизации хвостового вызова из-за его операции сложения, которая требует, чтобы среда выполнения рекурсивно вызывала функцию, брала возвращаемое значение, добавляла к нему 1 и возвращала результат. Для этого требуется место в стеке, а это означает, что если список достаточно длинный, у вас может закончиться стек.
Вместо этого рассмотрите этот подход:
list_length(L) -> list_length(L, 0).
list_length([], Acc) -> Acc;
list_length([_|Xs], Acc) -> list_length(Xs, Acc+1).
Этот подход, очень распространенный в коде Erlang, создает аккумулятор в list_length/1
для хранения значения длины, инициализирует его значением 0 и передает в list_length/2
, который выполняет рекурсию. Затем каждый вызов list_length/2
увеличивает аккумулятор, и когда список пуст, первое предложение list_length/2
возвращает аккумулятор в качестве результата. Но обратите внимание, что операция сложения здесь происходит до рекурсивного вызова, что означает, что вызовы являются истинными хвостовыми вызовами и, таким образом, не требуют дополнительного пространства стека.
Для начинающих программистов на Erlang может быть полезно скомпилировать как исходную, так и модифицированную версии этого модуля с помощью erlc -S
и изучить сгенерированный ассемблер Erlang. В исходной версии ассемблер содержит allocate
вызовов для пространства стека и использует call
для рекурсивного вызова, где call
— инструкция для обычных вызовов функций. Но для этой модифицированной версии вызовы allocate
не генерируются, и вместо использования call
выполняется рекурсия с использованием call_only
, которая оптимизирована для хвостовых вызовов.
person
Steve Vinoski
schedule
25.04.2015