[英]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.