Both `foldl` and `foldr` 处理列表时都是从第一个元素到最后一个元素,但它们在结合结合操作的方式上有所不同。对于 `foldl`,操作是左结合的,构建表达式 (((v ⊗ e0) ⊗ e1) ⊗ e2) …,而 `foldr` 是右结合的,产生 e0 ⊗ (e1 ⊗ (e2 ⊗ … (en ⊗ v)…))。列表元素在两种表达式中出现的顺序相同;只有应用的分组方式改变。(来源:主要)
在严格语言中,求值从内向外进行。对于 `foldl`,需要立即获取第一个列表元素,这允许使用常量栈空间的尾递归实现。相比之下,`foldr` 必须先到达列表末尾才能开始任何规约,因此它必然会构建一个与列表长度成比例的调用栈。当结合操作满足结合律和交换律(例如 (+))时,两个函数会得到相同的最终结果,但规约序列不同。(来源:主要)
在像 Haskell 这样的惰性语言中,求值从外向内进行,仅在需要时要求结果。在这里,`foldl` 会变得 problematic:每一步都会为部分应用的操作创建一个 thunk,而由于最外层的 thunk 不会被强制求值直到结束,大小与列表呈线性关系的 thunk 链会累积,导致线性空间消耗。严格变体 `foldl'` 在每一步都强制求值累加器,从而防止 thunk 堆积并恢复常量空间行为。相反,`foldr` 在其第二个参数上可以是惰性的;如果结合函数在那里是惰性的,`foldr` 可以在不遍历整个列表的情况下返回弱头范式结果,从而实现节省计算并能够处理无限列表。(来源:主要)
作为经验法则,当累积函数是严格的时使用 `foldl'`,当函数在其第二个参数上是惰性的时使用 `foldr`;在列表上应避免使用普通的 `foldl` 和 `foldr'`。对于其他数据结构,最佳选择取决于它们的结合律,而在结合律无关时,`foldMap`/`foldMap'` 通常是更好的选择。(来源:主要)
