Сгибы против рекурсии в Erlang

по данным узнать вам некоторые Erlang :

практически любая функция, которую вы можете придумать, которая уменьшает списки до 1 элемента, может быть выражена как складка. [...] Это означает, что fold универсален в том смысле, что вы можете реализовать практически любую другую рекурсивную функцию в списках с fold

моя первая мысль при написании функции, которая принимает списки и уменьшает их до 1 элемента, - использовать рекурсию.

каковы рекомендации, которые должны помочь мне решить, использовать ли рекурсию или сгиб?

это стилистическое соображение или есть и другие факторы (производительность,читаемость и т. д.)?

3 ответов


складки обычно более читабельны (так как все знают, что они делают) и быстрее из-за оптимизированных реализаций во время выполнения (особенно foldl, который всегда должен быть хвостовым рекурсивным). Стоит отметить, что они являются только постоянным фактором быстрее, а не на другом заказе, поэтому обычно преждевременная оптимизация, если вы рассматриваете один над другим по соображениям производительности.

используйте стандартную рекурсию, когда вы делаете причудливые вещи, такие как работа над более чем по одному элементу за раз, разбив на несколько процессов и похожих, и придерживайтесь функций более высокого порядка (fold, map, ... когда они уже делают то, что ты хочешь.


Я лично предпочитаю рекурсию по сгибу в Erlang (в отличие от других языков, например Haskell). Я не вижу fold более читаемым, чем рекурсия. Например:

fsum(L) -> lists:foldl(fun(X,S) -> S+X end, 0, L).

или

fsum(L) ->
    F = fun(X,S) -> S+X end,
    lists:foldl(F, 0, L).

vs

rsum(L) -> rsum(L, 0).

rsum([], S) -> S;
rsum([H|T], S) -> rsum(T, H+S).

кажется больше кода, но это довольно простой и идиоматический Erlang. Использование fold требует меньше кода, но разница становится все меньше и меньше с большей полезной нагрузкой. Представьте, что нам нужен фильтр и сопоставить нечетные значения с их квадрат.

lcfoo(L) -> [ X*X || X<-L, X band 1 =:= 1].

fmfoo(L) ->
  lists:map(fun(X) -> X*X end,
    lists:filter(fun(X) when X band 1 =:= 1 -> true; (_) -> false end, L)).

ffoo(L) -> lists:foldr(
    fun(X, A) when X band 1 =:= 1 -> [X|A];
      (_, A) -> A end,
    [], L).

rfoo([]) -> [];
rfoo([H|T]) when H band 1 =:= 1 -> [H*H | rfoo(T)];
rfoo([_|T]) -> rfoo(T).

здесь побеждает понимание списка, но рекурсивная функция находится на втором месте, а версия fold уродлива и менее читаема.

и, наконец, неверно, что fold быстрее рекурсивной версии, особенно при компиляции в собственный (HiPE) код.

редактировать: Я добавляю версию fold с забавой в переменной по запросу:

ffoo2(L) ->
    F = fun(X, A) when X band 1 =:= 1 -> [X|A];
           (_, A) -> A
        end,
    lists:foldr(F, [], L).

Я не вижу, как это более читабельным, чем rfoo/1 и я нашел особенно манипуляция аккумулятором сложнее и менее очевидна, чем прямая рекурсия. Это еще более длинный код.


Я ожидаю, что fold выполняется рекурсивно, поэтому вы можете посмотреть на попытку реализовать некоторые из различных функций списка, таких как map или filter, с fold, и посмотреть, насколько это может быть полезно.

в противном случае, если вы делаете это рекурсивно, вы можете повторно реализовать fold, в основном.

научиться использовать то, что происходит с языком, - это моя мысль.

Это обсуждение foldl и рекурсии интересно:

простой способ сломать foldl

Если вы посмотрите на первый пункт в этом введении (вы можете прочитать все это), он говорит лучше, чем я.

http://www.cs.nott.ac.uk / ~gmh / fold.pdf