fold 如何区分 Haskell 中的 x 和 xs?

How does fold distinguish x from xs in Haskell?

sum' :: (Num a) => [a] -> a
sum' xs = foldl (\acc x -> acc + x) 0 xs

没有 x:xs 这样的模式。 xs 是一个列表。在lambda函数中,表达式acc + x如何知道xxs中的元素?

how does the expression acc + x knows that x is the element in xs?

没有。它计算传递给它的任何内容的总和。

注意(\acc x -> acc + x)可以简单写成(+)

There is no pattern like x:xs. xs is a list. In the lambda function, how does the expression acc + x knows that x is the element in xs?

在 Haskell 中 - 就像在许多编程语言中一样 - 变量的名称无关紧要。对于 Haskell,写 xsxacc 或使用其他标识符都没有关系。这里重要的是参数的位置。

foldl :: (a -> b -> a) -> a -> [b] -> a 是一个函数,它将类型为 a -> b -> a 的函数作为输入,然后是类型 a 的对象,然后是类型为 [= 的元素列表22=] 和 returns 类型的对象 a.

从语义上讲,函数的 second 参数将是列表的元素。如果您这样写 \x acc -> x + accacc 将是列表的元素,而 x 是累加器。

这个绑定的原因是因为 foldl 是这样实现的:

foldl f z [] = z
foldl f z (x:xs) = foldl f (f z x) xs

因此在Haskell中定义了自己,从而将函数绑定到f,初始元素绑定到z,并进行递归最终得到结果递归调用,我们取列表的尾部,并使用 (f z x) 作为新的初始值,直到列表耗尽。

你可以把总和写得更优雅:

sum' :: Num n => [n] -> n
sum' = foldl (+) 0

所以这里根本没有使用显式变量。

它没有 "know" 那样的东西 - 这里没有魔法。

foldl的定义等价于:

foldl f acc (x:xs) = foldl f (f acc x) xs
foldl _ acc [] = acc

所以通过一个使用您的 sum' 函数的简单示例:

我们从

开始
sum' [1,2,3]

代入sum'的定义我们得到

foldl (\acc x -> acc + x) 0 [1,2,3]

代入foldl的定义(第一种情况):

foldl (\acc x -> acc + x) ((\acc x -> acc + x) 0 1) [2,3]

评估你的lambda的函数应用,我们得到

foldl (\acc x -> acc + x) (0 + 1) [2,3]

再次替换 foldl...

foldl (\acc x -> acc + x) ((\acc x -> acc + x) (0+1) 2) [3]

并评估累加器:

foldl (\acc x -> acc + x) ((0 + 1) + 2) [3]

并再次替换 foldl...

foldl (\acc x -> acc + x) ((\acc x -> acc + x) ((0 + 1) + 2) 3) []

再次计算累加器:

foldl (\acc x -> acc + x) (((0 + 1) + 2) + 3) []

现在我们进入 foldl 的第二个(终止)案例,因为我们将它应用于一个空列表并且只剩下:

(((0 + 1) + 2 ) + 3)

我们当然可以计算得到 6.

如您所见,这里没有任何魔法:x 只是您为函数参数指定的名称。您可以将其命名为 user8314628,它的工作方式相同。将列表头部的值绑定到该参数的不是您自己匹配的任何模式,而是 foldl 实际对列表所做的事情。

请注意,您可以使用此分步过程评估任何 haskell 表达式;您通常不必这样做,但是对于执行或多或少复杂的事情并且您不熟悉的函数,这样做几次很有用。

折叠获取输入列表的每个连续值,同时将余数传递回函数透明。如果您要编写自己的 sum’ 函数,则必须将余数传回您的函数。您还必须将 累加器 传递回您自己的函数以保持 运行 总数。 Fold 不会通过获取第一个值并传递余数来明确处理列表。它所说明的是累加器。在求和函数的情况下,它也必须保持 运行 总数。累加器是显式的,因为一些递归函数可能用它做不同的事情。