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
如何知道x
是xs
中的元素?
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,写 xs
、x
、acc
或使用其他标识符都没有关系。这里重要的是参数的位置。
foldl :: (a -> b -> a) -> a -> [b] -> a
是一个函数,它将类型为 a -> b -> a
的函数作为输入,然后是类型 a
的对象,然后是类型为 [= 的元素列表22=] 和 returns 类型的对象 a
.
从语义上讲,函数的 second 参数将是列表的元素。如果您这样写 \x acc -> x + acc
,acc
将是列表的元素,而 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 不会通过获取第一个值并传递余数来明确处理列表。它所说明的是累加器。在求和函数的情况下,它也必须保持 运行 总数。累加器是显式的,因为一些递归函数可能用它做不同的事情。
sum' :: (Num a) => [a] -> a
sum' xs = foldl (\acc x -> acc + x) 0 xs
没有 x:xs
这样的模式。 xs
是一个列表。在lambda函数中,表达式acc + x
如何知道x
是xs
中的元素?
how does the expression
acc + x
knows thatx
is the element inxs
?
没有。它计算传递给它的任何内容的总和。
注意(\acc x -> acc + x)
可以简单写成(+)
。
There is no pattern like
x:xs
. xs is a list. In the lambda function, how does the expressionacc + x
knows thatx
is the element inxs
?
在 Haskell 中 - 就像在许多编程语言中一样 - 变量的名称无关紧要。对于 Haskell,写 xs
、x
、acc
或使用其他标识符都没有关系。这里重要的是参数的位置。
foldl :: (a -> b -> a) -> a -> [b] -> a
是一个函数,它将类型为 a -> b -> a
的函数作为输入,然后是类型 a
的对象,然后是类型为 [= 的元素列表22=] 和 returns 类型的对象 a
.
从语义上讲,函数的 second 参数将是列表的元素。如果您这样写 \x acc -> x + acc
,acc
将是列表的元素,而 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 不会通过获取第一个值并传递余数来明确处理列表。它所说明的是累加器。在求和函数的情况下,它也必须保持 运行 总数。累加器是显式的,因为一些递归函数可能用它做不同的事情。