sum' :: (Num a) => [a] -> a
sum' xs = foldl (\acc x -> acc + x) 0 xs
没有像x:xs
这样的模式。 xs
是一个列表。在lambda函数中,表达式acc + x
如何知道x
是xs
中的元素?
答案 0 :(得分:5)
没有像
x:xs
这样的模式。 xs是一个列表。在lambda函数中,表达式acc + x
如何知道x
是xs
中的元素?
在Haskell中(就像许多编程语言一样),变量的名称无关紧要。对于Haskell,您写xs
还是x
或acc
还是使用另一个标识符都没有关系。实际上,这里重要的是参数的位置。
foldl :: (a -> b -> a) -> a -> [b] -> a
是一个函数,其输入类型为a -> b -> a
的函数,后跟类型为a
的对象,后跟类型为{{1}的元素列表},并返回类型为b
的对象。
功能上,该函数的 second 参数将成为列表的元素。如果您这样写a
,则\x acc -> x + acc
将成为列表的元素,acc
将成为累加器。
之所以绑定,是因为x
的实现方式如下:
foldl
因此,它是在Haskell中定义的,因此将函数绑定到foldl f z [] = z
foldl f z (x:xs) = foldl f (f z x) xs
,将初始元素绑定到f
,并执行递归以最终通过在我们进行调用的recurslive调用中获得结果。列表的末尾,并使用z
作为新的初始值,直到列表用尽。
您可以将总和写得更优雅:
(f z x)
所以这里根本没有使用显式变量。
答案 1 :(得分:2)
它不“知道”这样的东西-这里没有魔术。
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 (\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表达式;您通常不必这样做,但是使用功能或多或少会执行一些复杂的功能并且您不熟悉该功能几次会很有用。
答案 2 :(得分:1)
表达式
acc + x
如何知道x
是xs
中的元素?
不是。它计算传递给它的任何东西的总和。
请注意,(\acc x -> acc + x)
可以简单地写为(+)
。
答案 3 :(得分:0)
折叠采用输入列表的每个连续值,同时将其余部分传递回透明的函数。如果要编写自己的sum’
函数,则必须将其余部分传递回函数。您还必须将累加器返回给自己的函数,以保持总计。折叠不会通过获取第一个值并传递其余值来显式处理列表。它所做的就是累加器。对于求和函数,它还必须保持运行总计。累加器是显式的,因为某些递归函数可能会对它执行不同的操作。