请记住,在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,并从中获得一些性能提升。