请记住,在Haskell中,由于延迟计算,您可以使用无限列表。因此,即使`[1 ..]无限长,head [1..]也只是1和head $ map (+1) [1..]2。如果没有得到,请停下来玩一会儿。如果您明白了,请继续阅读...
我觉得你的困惑的部分是,foldl和foldr总是从一侧或另一侧开始,因此您无需花太多时间。
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..])
与(根据我们对fold的定义)相同
testFold = dumbFunc 1 $ foldr dumbFunc "" [2..]
现在,根据dumbFunc我们的定义,我们可以得出结论
testFold = "always returns the same string"
当我们具有执行某些功能但有时很懒的功能时,这会更有趣。例如
foldr (||) False
用于查找列表是否包含任何True元素。我们可以使用它来定义高阶函数any,True当且仅当传入的函数对于列表的某些元素为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,foldr但反之则不能。这意味着以某种深刻的方式foldr是所有高阶字符串函数中最基础的,因为它是我们用来实现几乎所有其他函数的函数。您仍然可能需要使用foldl有时候,因为您可以foldl递归实现tail,并从中获得一些性能提升。