我无法理解为什么以下函数会导致无限循环:
import Data.List
isTrue = foldl' (&&) False (repeat False)
答案 0 :(得分:13)
foldl
和foldl'
都是以这样的方式定义的:它们必须经过整个列表才能产生部分结果(实际上没有办法将它们定义为情况并非如此)。所以既不适用于无限列表。
答案 1 :(得分:5)
这些是普通foldl
和repeat
的定义:
foldl :: (b -> a -> b) -> b -> [a] -> b
foldl f z [] = z
foldl f z (x:xs) = foldl f (f z x) xs
repeat :: a -> [a]
repeat a = a : repeat a
现在,当我们尝试定义isTrue
时会发生什么? (当然,改编为懒惰的foldl
,但这与你的问题相同。)
foldl (&&) False (repeat False)
== foldl (&&) False (False : repeat False)
== foldl (&&) (False && False) (repeat False)
现在这是关键时刻。评估如何从这里继续?好吧,这是一个foldl
thunk,所以我们必须弄清楚要使用的两个foldl方程式中的哪个 - 具有[]
模式的方程式,或者具有x:xs
的方程式。这意味着我们必须强制repeat False
查看它是空列表还是一对:
== foldl (&&) (False && False) (False : repeat False)
== foldl (&&) ((False && False) && False) (repeat False)
......它会继续这样做。基本上,foldl
只能在遇到[]
时终止,而repeat
永远不会产生[]
。
== foldl (&&) ((False && False) && False) (False : repeat False)
== foldl (&&) (((False && False) && False) && False) (repeat False)
...
使用严格foldl'
意味着False && False
项减少到False
,因此代码将在恒定空间中运行。但它仍会继续,直到它看到[]
,它永远不会出现:
foldl' f z [] = z
foldl' f z (x:xs) =
let z' = f z x
in z' `seq` foldl' f z' xs
foldl' (&&) False (repeat False)
== foldl' (&&) False (False : repeat False)
== let z' = False && False in z' `seq` foldl' (&&) z' (repeat False)
-- We reduce the seq by forcing z' and substituting its result into the
-- its second argument. Which takes us right back to where we started...
== foldl' (&&) False (repeat False)
...
这些函数没有任何智能,可以让他们看到累加器永远不会是False
以外的任何东西。 foldl'
对(&&)
和repeat False
的工作原理一无所知。所有它知道的都是列表,它只会在空的列表上完成。
在Haskell中,由于懒惰,通常是相反的方式,因此foldl
和foldl'
是很糟糕的,而foldr
'是“好的”。例如,以下内容将终止:
foldr (&&) False (repeat False)
为什么呢?以下是foldr
:
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr f z = []
foldr f z (x:xs) = f x (foldr f z xs)
将此处的第二个等式与foldl
的等式进行比较; foldl'
是尾递归,而foldr
尾调用f
并将递归foldr
调用作为参数传递。这意味着f
可以选择是否以及何时递归foldr
;如果f
在其第二个参数上是惰性的,那么递归将被推迟,直到需要它的结果。如果f
丢弃它的第二个参数,那么我们永远不会递归。
所以,适用于我的例子:
foldr (&&) False (repeat False)
== foldr (&&) False (False : repeat False)
== False && foldr (&&) False (repeat False)
== False
我们已经完成了!但请注意,这只能起作用,因为(&&)
在其第一个参数中是严格的,如果第一个参数是False
,则丢弃其第二个参数。以下变化进入无限循环:
foldr (flip (&&)) False (repeat False)