请记住,在 Haskell 中,由于惰性求值,您可以使用无限列表。所以,head [1..]
只是 1,并且head $ map (+1) [1..]
是 2,即使 `[1..] 是无限长的。如果你不明白,停下来玩一会儿。如果你明白了,请继续阅读......
我认为您的部分困惑是foldl
andfoldr
总是从一侧或另一侧开始,因此您不需要给出长度。
foldr
有一个非常简单的定义
foldr _ z [] = z
foldr f z (x:xs) = f x $ foldr f z xs
为什么这会在无限列表上终止,试试
dumbFunc :: a -> b -> String
dumbFunc _ _ = "always returns the same string"
testFold = foldr dumbFunc 0 [1..]
在这里,我们传入foldr
一个“”(因为值无关紧要)和自然数的无限列表。这会终止吗?是的。
它终止的原因是因为 Haskell 的求值相当于惰性术语重写。
所以
testFold = foldr dumbFunc "" [1..]
变成(允许模式匹配)
testFold = foldr dumbFunc "" (1:[2..])
这与(根据我们对折叠的定义)相同
testFold = dumbFunc 1 $ foldr dumbFunc "" [2..]
现在根据定义,dumbFunc
我们可以得出结论
testFold = "always returns the same string"
当我们有功能做某事但有时很懒惰时,这会更有趣。例如
foldr (||) False
用于查找列表是否包含任何True
元素。我们可以使用它来定义高阶函数n,当且仅当传入的函数对于列表的某些元素为真时才any
返回True
any :: (a -> Bool) -> [a] -> Bool
any f = (foldr (||) False) . (map f)
惰性求值的好处是,它会在遇到第一个元素时停止,e
这样f e == True
另一方面,这不是真的foldl
。为什么?那么一个非常简单的foldl
看起来像
foldl f z [] = z
foldl f z (x:xs) = foldl f (f z x) xs
现在,如果我们尝试上面的示例会发生什么
testFold' = foldl dumbFunc "" [1..]
testFold' = foldl dumbFunc "" (1:[2..])
现在变成:
testFold' = foldl dumbFunc (dumbFunc "" 1) [2..]
所以
testFold' = foldl dumbFunc (dumbFunc (dumbFunc "" 1) 2) [3..]
testFold' = foldl dumbFunc (dumbFunc (dumbFunc (dumbFunc "" 1) 2) 3) [4..]
testFold' = foldl dumbFunc (dumbFunc (dumbFunc (dumbFunc (dumbFunc "" 1) 2) 3) 4) [5..]
等等等等。我们永远无法到达任何地方,因为 Haskell 总是首先评估最外层的函数(简而言之就是惰性评估)。
这样做的一个很酷的结果是您可以实现foldl
out offoldr
但反之则不行。这意味着以某种深刻的方式foldr
是所有高阶字符串函数中最基本的,因为它是我们用来实现几乎所有其他函数的函数。有时您可能仍想使用 a foldl
,因为您可以递归地实现foldl
tail,并从中获得一些性能提升。