繁体   English   中英

了解 foldr 和 foldl 函数

[英]Understanding foldr and foldl functions

foldr :: (a -> b -> b) -> b -> [a] -> b
foldr f v [] = v
foldr f v (x:xs) = f x (foldr f v xs)
foldl :: (a -> b -> a) -> a -> [b] -> a
foldl f v [] = v
foldl f v (x:xs) = foldl f (f v x) xs

我正试图围绕这两个功能展开思考。 我有两个问题。 一个关于函数f 一般来说,

foldr f v xs

f可以访问xs的第一个元素和递归处理的尾部。 这里:

foldl f v xs

f可以访问 xs 的最后一个元素和递归处理的尾部。

这是一种有用(且正确)的思考方式吗?

我的第二个问题与折叠“右”或“左”有关。 在很多地方,他们说 foldr“从右边开始”。 例如,如果我扩展表达式

foldr (+) 0 [1,2,3]

我明白了

(+) 1 (foldr (+) 0 [2,3])

所以,我看到它是“从列表的左边开始”。 第一个元素和递归处理的尾部是函数的参数。 有人可以对这个问题有所了解吗?

编辑:我的问题之一是传递给fold的函数f 链接的答案没有解决这一点。

“从右边开始”是很好的基本直觉,但它也可能会产生误导,正如您刚刚发现的那样。 事实上,Haskell 中的列表是单链接的,我们只能直接访问一侧,所以在某种意义上,Haskell 中的每个列表操作都是从左侧“开始”的。 但它从那里做什么才是重要的。 让我们完成扩展您的foldr示例。

foldr (+) 0 [1, 2, 3]
1 + foldr 0 [2, 3]
1 + (2 + foldr 0 [3])
1 + (2 + (3 + foldr 0 []))
1 + (2 + (3 + 0))

现在foldl也是如此。

foldl (+) 0 [1, 2, 3]
foldl (+) (0 + 1) [2, 3]
foldl (+) ((0 + 1) + 2) [3]
foldl (+) (((0 + 1) + 2) + 3) []
((0 + 1) + 2) + 3

foldr的情况下,我们直接进行递归调用,因此我们将头部作为累加函数的参数,然后将另一个参数作为递归调用。

foldl情况下,我们通过更改累加器参数来进行递归调用。 由于我们更改的是参数而不是结果,因此评估的顺序会颠倒过来。

不同之处在于括号“关联”的方式。 foldr情况下,括号与右侧相关联,而在foldl情况下,括号与左侧相关联。 同样,“初始”值在foldr的右侧和foldl的左侧。

在 Haskell 的列表中使用折叠的一般建议是这样的。

  • 如果您想要尊重列表结构的惰性评估,请使用foldr 由于foldr在函数调用内部进行递归,所以如果折叠函数恰好被保护(即通过数据构造函数),那么我们的foldr调用被保护。 例如,我们可以使用foldr从另一个无限列表中有效地构造一个无限列表。

  • 使用foldl' (注意末尾的' ),严格的左折叠,用于您希望操作严格的情况。 foldl'在继续之前强制折叠的每一步都变成弱头部正常形式,以防止 thunk 积聚。 因此,虽然foldl将构建整个内部表达式,然后可能在最后对其进行评估,但foldl'将在我们进行时完成工作,这在大型列表上节省了大量内存。

  • 不要在列表中使用foldl foldl获得的惰性几乎没有用处,因为从左折叠中获得任何有用的唯一方法是强制整个折叠无论如何,在内部建立 thunk 是没有用的。

对于其他不右偏的数据结构,规则可能不同。 所有这些都是在您的Foldable是 Haskell 列表的假设下运行的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM