В поисках критики моей программы Erlang
Я новичок в Erlang и довольно плохо знаком с функциональным программированием в целом.
До сих пор я действительно хорошо проводил время с Эрлангом (хотя пунктуация Эрланга несколько раз приводила меня в замешательство;)), но мне бы очень понравилось, если бы я мог получить отзыв о своем коде от более опытного Эрланга программисты.
Кажется, мой код работает нормально, но я уверен, что вы, ребята, могли бы дать много советов по улучшению!:)
Вот программа для решения 2-й задачи Эйлера проекта (найдите сумму всех четных простых чисел ниже четырех миллионов), разделенная на два модуля:
-module(seqs).
-export([takewhile/2, take/2]).
%% Recursively pick elements from a lazy sequence while Pred(H) is true
takewhile(Pred, [H|T]) ->
case Pred(H) of
true -> [H|takewhile(Pred, T())];
false -> []
end.
%% Take a certain number of elements from a lazy sequence
%% A non-tail recursive version
take(0, _) -> [];
take(Number, [H|T]) ->
[H|take(Number - 1, T())].
Второй модуль решает актуальную проблему:
-module(euler002).
-import(seqs, [takewhile/2]).
-export([lazyfib/0, solve/0]).
%% Sums the numbers in a list (for practice's sake)
sum([]) -> 0;
sum([H|T]) -> H + sum(T).
%% Practicing some list comprehensions as well!
filter(P, Xs) -> [ X || X <- Xs, P(X) ].
%% Lazy sequence that generates fibonacci numbers
lazyfib(A, B) -> [A | fun () -> lazyfib(B, A + B) end].
lazyfib() -> lazyfib(0, 1).
%% Generate all fibonacci terms that are less than 4 million and sum the
%% even terms
solve() ->
Fibs = seqs:takewhile(fun (X) -> X < 4000000 end, lazyfib()),
sum(filter(fun (X) -> X rem 2 =:= 0 end, Fibs)).
Заранее спасибо, и, пожалуйста, скажите мне, если это не подходящий форум для такого рода вопросов!:)
3 ответа
Вот общий совет. Вы должны попытаться использовать хвостовую рекурсию, когда это возможно (опять же, ради практики).
Например, эта функция не является хвостовой рекурсией:
%% Sums the numbers in a list (for practice's sake)
sum([]) -> 0;
sum([H|T]) -> H + sum(T).
потому что результат sum(T)
должен быть возвращен, чтобы его можно было добавить к H
, Каждый рекурсивный вызов добавляет к стеку вызовов. Вам не хватит памяти или произойдет сбой, если ваш список слишком большой, как при суммировании многих простых чисел.
Чтобы сделать функцию хвостовой рекурсивной, используйте такой аккумулятор:
%% Hide the use of the accumulator, so caller can still use sum/1.
%% Also note the period ending this function definition.
sum(List) -> sum(List, 0).
%% Sums the numbers in a list (for practice's sake)
sum([], Acc) -> Acc;
sum([H|T], Acc) -> sum(T, H + Acc).
Хотя это выглядит примерно так, обратите внимание, что добавление выполняется перед рекурсивным вызовом, который заканчивается последней инструкцией. Это означает, что компилятор может оптимизировать его как переход, а не вызов (так как он никогда не должен возвращаться к этой функции), так что вы не создаете огромный стэк вызовов.
Также будьте осторожны с пунктуацией при переходе от sum/1
в sum/2
, Функции с разной арностью различны, даже если они имеют одинаковые имена. Вот почему первая строка заканчивается точкой, а не точкой с запятой.
Надеюсь, это поможет. Удачи.
Я понимаю всю прелесть обобщенного функционального подхода с итераторами и генераторами, но в любом случае простое решение гораздо проще и приятнее:
-module(euler).
-export([euler2/1]).
euler2(Limit) -> euler2(Limit, 0, 1, 1).
euler2(Limit, Sum, A, _B) when A > Limit -> Sum;
euler2(Limit, Sum, A, B) ->
NewSum = case A rem 2 of 0 -> Sum+A; _ -> Sum end,
euler2(Limit, NewSum, B, A+B).
Учитывая запуск вашего кода через Tidier.
http://tidier.softlab.ntua.gr:20000/tidier/getstarted
Я видел короткую презентацию, и некоторые из стилей кода и оптимизации LOC, которые он сделал, были просто поразительными.