2

Я новичок в Erlang и довольно новичок в функциональном программировании в целом.Ищете критику моей программы Erlang

У меня было очень хорошее время с Эрланом (хотя пунктуация Эрланга заставила меня побывать несколько раз;)), но мне очень понравилось бы, если бы я мог получить некоторую обратную связь на my кода от более опытных программистов Erlang.

Мой код, похоже, работает нормально, но я уверен, что вы, ребята, могли бы предложить много советов для улучшения! :)

Вот программа для решения 2nd Project Euler problem (найти сумму всех четных простых чисел ниже четырех миллионов), разделить на два модуля:

-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)). 

Заранее благодарим за участие в обсуждении этого вопроса, пожалуйста, сообщите мне об этом. :)

+0

Является ли ваша функция TakeWhile действительно не отличается от списков: TakeWhile/2? Также ваша функция приема похожа на списки: sublist/2.Возможно, кто-то может исправить меня по этому поводу, но я не думаю, что ваши функции «seqs» действительно ленивы в том смысле, что, например, Хаскелл использует лень. Например, у Erlang нет бесконечных списков. –

ответ

2

Вот общий совет. Вы должны попытаться использовать хвостовую рекурсию, когда это возможно (опять же, ради практики).

Например, эта функция не хвостовая рекурсия:

%% 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. Функции с различной ясностью различны, даже если они имеют одно и то же имя. Вот почему первая строка заканчивается точкой, а не точкой с запятой.

Надеюсь, что это поможет. Удачи.

+0

Спасибо! :) Я пытаюсь решить последующие проблемы, используя хвостовую рекурсию. Пунктуация в Эрланге мне показалась плохим несколько раз, но я привык к этому сейчас. Я обнаружил, что пунктуация также имеет свои преимущества - это позволяет режиму Erlang для Emacs вести себя разумно, для одного :) – Christoffer

0

Принимая во внимание ваш код через Tidier.

http://tidier.softlab.ntua.gr:20000/tidier/getstarted

я увидел короткую презентацию на нем, и некоторые из кода стиля и LOC оптимизаций это сделал, были просто поразительны.

+0

Это довольно хороший инструмент! Я попробовал это с моим последним файлом, и, хотя он не менял никакого кода, он наверняка сделал его красивее! ;) (Я бы поднял ваши ответы, но у меня пока нет репутации: P) – Christoffer

1

Я понимаю красоту обобщенного функционального подхода с итераторами и генераторами, но в любом случае прямым решением гораздо проще и приятно:

-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). 
Смежные вопросы