Erlang中的折叠与递归

uoifb46i  于 2022-12-08  发布在  Erlang
关注(0)|答案(4)|浏览(178)

根据Learn you some Erlang:
几乎任何你能想到的把列表减少到1个元素的函数都可以用fold来表示。[...]这意味着fold是通用的,你可以用fold在列表上实现几乎任何其他递归函数
当我编写一个函数,它接受一个列表并将其减少为1个元素时,我的第一个想法是使用递归。
有什么准则可以帮助我决定是使用递归还是折叠?
这是一个风格上的考虑,还是还有其他因素(性能、可读性等)?

wn9m85ua

wn9m85ua1#

我个人更喜欢Erlang中的递归而不是fold(与其他语言相反,例如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).

对比

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

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

看起来代码更多,但它是非常直接和惯用的Erlang。使用fold需要更少的代码,但差异变得越来越小的负载。想象一下,我们想要一个过滤器和Map奇数值到他们的平方。

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比递归版本更快的说法是不正确的,尤其是在编译为本机(HiPE)代码时。

Edit:我根据请求添加了一个文件夹版本,变量中包含fun:

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

我看不出它有什么比rfoo/1更好的可读性,我发现特别是一个累加器操作比直接递归更复杂,更不明显,它甚至是更长的代码。

lstz6jyr

lstz6jyr2#

foldl通常可读性更强(因为每个人都知道它们做什么),并且由于在运行时优化了实现(尤其是foldl,它总是应该是尾递归的),速度更快。值得注意的是,它们只是快了一个常数因子,而不是另一个顺序,所以如果你发现自己出于性能原因考虑一个而不是另一个,通常是过早的优化。
当你做一些花哨的事情时,比如一次处理多个元素,分成多个进程等等,使用标准递归,当你想做的事情已经完成时,坚持使用高阶函数(fold,map,...)。

lg40wkob

lg40wkob3#

我希望fold是递归完成的,所以您可能希望尝试使用fold实现一些不同的列表函数,比如map或filter,看看它有多有用。
否则,如果您递归地执行此操作,基本上可能会重新实现fold。
学会使用语言所附带的东西,是我的思想。
关于foldl和递归的讨论很有趣:
Easy way to break foldl
如果你看这篇导言的第一段(你可能想全部读一遍),他说得比我好。
http://www.cs.nott.ac.uk/~gmh/fold.pdf

fslejnso

fslejnso4#

旧线程,但我的经验是,折叠比递归函数工作得慢。

相关问题