В поисках критики моей программы 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, которые он сделал, были просто поразительными.

Другие вопросы по тегам