在Erlang中折叠与递归

时间:2012-03-30 00:52:14

标签: recursion erlang fold

根据Learn you some Erlang

  

您可以想到的任何将列表减少为1个元素的函数都可以表示为折叠。 [...]   这意味着fold是通用的,因为你可以使用fold

在列表上实现几乎任何其他递归函数

我在编写一个带有列表并将其减少为1个元素的函数时的第一个想法是使用递归。

有哪些指导方针可以帮助我决定是使用递归还是折叠?

这是一种风格考虑还是还有其他因素(性能,可读性等)?

3 个答案:

答案 0 :(得分:8)

我个人更喜欢Erlang中的递归折叠(与其他语言相反,例如Haskell)。我没有看到折叠比递归更具可读性。例如:

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。使用折叠需要更少的代码,但差异越来越小,有效载荷越来越大。想象一下,我们想要一个过滤器并将奇数值映射到它们的正方形。

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

这里列表理解获胜,但递归函数位于第二位,折叠版本丑陋且可读性较差。

最后,折叠比递归版更快,尤其是在编译为本机(HiPE)代码时。

修改: 我根据要求在变量中添加了一个有趣的折叠版本:

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

我不知道它比rfoo/1更具可读性,我发现特别是累加器操作比直接递归更复杂,更不明显。它的代码更长。

答案 1 :(得分:7)

由于运行时优化的实现(尤其是foldl总是应该是尾递归的),因此折叠通常更具可读性(因为每个人都知道它们的作用)并且更快。值得注意的是,它们只是一个恒定的因素,而不是另一个顺序,所以如果你出于性能原因发现自己考虑一个而不是另一个,那么它通常会过早优化。

当你做一些奇特的东西时使用标准递归,例如一次处理多个元素,分成多个进程和类似的东西,并且当它们已经存在时,坚持使用高阶函数(fold,map,...)做你想做的事。

答案 2 :(得分:2)

我希望fold是递归完成的,所以你可能想看看尝试用fold实现一些各种列表函数,比如map或filter,看看它有多么有用。

否则,如果你以递归方式执行此操作,基本上可能会重新实现折叠。

学习使用语言附带的内容,是我的想法。

关于foldl和递归的讨论很有意思:

Easy way to break foldl

如果你看一下这篇介绍的第一段(你可能想要阅读全部内容),他说的比我更好。

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