【发布时间】:2012-04-13 16:57:54
【问题描述】:
几乎任何你能想到的将列表减少到 1 个元素的函数都可以表示为折叠。 [...] 这意味着 fold 是通用的,因为您可以在带有折叠的列表上实现几乎任何其他递归函数
在编写一个接受列表并将其减少为 1 个元素的函数时,我的第一个想法是使用递归。
哪些准则可以帮助我决定是使用递归还是折叠?
这是一种风格方面的考虑,还是还有其他因素(性能、可读性等)?
【问题讨论】:
几乎任何你能想到的将列表减少到 1 个元素的函数都可以表示为折叠。 [...] 这意味着 fold 是通用的,因为您可以在带有折叠的列表上实现几乎任何其他递归函数
在编写一个接受列表并将其减少为 1 个元素的函数时,我的第一个想法是使用递归。
哪些准则可以帮助我决定是使用递归还是折叠?
这是一种风格方面的考虑,还是还有其他因素(性能、可读性等)?
【问题讨论】:
我希望 fold 是递归完成的,因此您可能希望尝试使用 fold 实现一些不同的列表函数,例如 map 或 filter,看看它有多大用处。
否则,如果您以递归方式执行此操作,则基本上您可能会重新实现折叠。
学习使用语言附带的东西,这是我的想法。
这个关于折叠和递归的讨论很有趣:
如果您查看本介绍中的第一段(您可能想阅读所有内容),他的陈述比我做得更好。
【讨论】:
由于运行时中的优化实现,折叠通常更易读(因为每个人都知道他们在做什么)和更快(尤其是 foldl,它总是应该是尾递归的)。值得注意的是,它们只是更快的常数因素,而不是另一个顺序,因此如果您发现自己出于性能原因考虑其中一个而不是另一个,则通常是过早的优化。
当您做一些花哨的事情时使用标准递归,例如一次处理多个元素,拆分为多个进程和类似的,并坚持使用高阶函数(折叠、映射、...)做你想做的事。
【讨论】:
我个人更喜欢 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).
对
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).
这里列表理解获胜,但递归函数排在第二位,折叠版本丑陋且可读性差。
最后,折叠比递归版本更快是不正确的,尤其是在编译为本机 (HiPE) 代码时。
编辑: 我根据要求在变量中添加了一个有趣的折叠版本:
ffoo2(L) ->
F = fun(X, A) when X band 1 =:= 1 -> [X|A];
(_, A) -> A
end,
lists:foldr(F, [], L).
我看不出它比rfoo/1 更具可读性,我发现尤其是累加器操作比直接递归更复杂且不那么明显。代码更长。
【讨论】:
lists:zf/2。这是一个地图过滤器。使用 zf 你可以做lists:zf(fun(X) when X band 1 =:= 1 -> {true, X * X}; (_) -> false end, L)。不幸的是 zf 没有记录,所以没有正式存在......
旧线程,但我的经验是 fold 比递归函数慢。
【讨论】: