简体   繁体   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

I am trying to wrap my head around this two functions.我正试图围绕这两个功能展开思考。 I have two questions.我有两个问题。 One regarding function f .一个关于函数f In general,一般来说,

foldr f v xs

f has access to the first element of xs and the recursively processed tail. f可以访问xs的第一个元素和递归处理的尾部。 Here:这里:

foldl f v xs

f has access to the last element of xs and the recursively processed tail. f可以访问 xs 的最后一个元素和递归处理的尾部。

Is this an useful (and correct) way to think about it ?这是一种有用(且正确)的思考方式吗?

My second question is related to fold "right" or "left".我的第二个问题与折叠“右”或“左”有关。 In many places, they say that foldr "starts from the right".在很多地方,他们说 foldr“从右边开始”。 For example, if I expand the expression例如,如果我扩展表达式

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

I get我明白了

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

So, I see it is "starting from the left" of the list.所以,我看到它是“从列表的左边开始”。 The first element and the recursively processed tail are the arguments to the function.第一个元素和递归处理的尾部是函数的参数。 Could someone give some light into this issue ?有人可以对这个问题有所了解吗?

EDIT: One of my question focuses is on the function f passed to fold ;编辑:我的问题之一是传递给fold的函数f the linked answer doesn't address that point.链接的答案没有解决这一点。

"Starting from the right" is good basic intuition, but it can also mislead, as you've already just discovered. “从右边开始”是很好的基本直觉,但它也可能会产生误导,正如您刚刚发现的那样。 The truth of the matter is that lists in Haskell are singly linked and we only have access to one side directly, so in some sense every list operation in Haskell "starts" from the left.事实上,Haskell 中的列表是单链接的,我们只能直接访问一侧,所以在某种意义上,Haskell 中的每个列表操作都是从左侧“开始”的。 But what it does from there is what's important.但它从那里做什么才是重要的。 Let's finish expanding your foldr example.让我们完成扩展您的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))

Now the same for foldl .现在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

In the foldr case, we make our recursive call directly, so we take the head and make it an argument to our accumulating function, and then we make the other argument our recursive call.foldr的情况下,我们直接进行递归调用,因此我们将头部作为累加函数的参数,然后将另一个参数作为递归调用。

In the foldl case, we make our recursive call by changing the the accumulator argument .foldl情况下,我们通过更改累加器参数来进行递归调用。 Since we're changing the argument rather than the result, the order of evaluation gets flipped around.由于我们更改的是参数而不是结果,因此评估的顺序会颠倒过来。

The difference is in the way the parentheses "associate".不同之处在于括号“关联”的方式。 In the foldr case, the parentheses associate to the right, while in the foldl case they associate to the left.foldr情况下,括号与右侧相关联,而在foldl情况下,括号与左侧相关联。 Likewise, the "initial" value is on the right for foldr and on the left for foldl .同样,“初始”值在foldr的右侧和foldl的左侧。

The general advice for the use of folds on lists in Haskell is this.在 Haskell 的列表中使用折叠的一般建议是这样的。

  • Use foldr if you want lazy evaluation that respects the list structure.如果您想要尊重列表结构的惰性评估,请使用foldr Since foldr does its recursion inside the function call, so if the folding function happens to be guarded (ie by a data constructor), then our foldr call is guarded.由于foldr在函数调用内部进行递归,所以如果折叠函数恰好被保护(即通过数据构造函数),那么我们的foldr调用被保护。 For instance, we can use foldr to efficiently construct an infinite list out of another infinite list.例如,我们可以使用foldr从另一个无限列表中有效地构造一个无限列表。

  • Use foldl' (note the ' at the end), the strict left fold, for situations where you want the operation to be strict.使用foldl' (注意末尾的' ),严格的左折叠,用于您希望操作严格的情况。 foldl' forces each step of the fold to weak head normal form before continuing, preventing thunks from building up. foldl'在继续之前强制折叠的每一步都变成弱头部正常形式,以防止 thunk 积聚。 So whereas foldl will build up the entire internal expression and then potentially evaluate it at the end, foldl' will do the work as we go, which saves a ton of memory on large lists.因此,虽然foldl将构建整个内部表达式,然后可能在最后对其进行评估,但foldl'将在我们进行时完成工作,这在大型列表上节省了大量内存。

  • Don't use foldl on lists.不要在列表中使用foldl The laziness gained by foldl is almost never useful, since the only way to get anything useful out of a left fold is to force the whole fold anyway, building up the thunks internally is not useful. foldl获得的惰性几乎没有用处,因为从左折叠中获得任何有用的唯一方法是强制整个折叠无论如何,在内部建立 thunk 是没有用的。

For other data structures which are not right-biased, the rules may be different.对于其他不右偏的数据结构,规则可能不同。 All of this is running on the assumption that your Foldable is a Haskell list.所有这些都是在您的Foldable是 Haskell 列表的假设下运行的。

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

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