严格版本的foldl无限运行

时间:2013-03-10 19:48:50

标签: haskell tail-recursion ghci

我无法理解为什么以下函数会导致无限循环:

import Data.List

isTrue = foldl' (&&) False (repeat False)

2 个答案:

答案 0 :(得分:13)

foldlfoldl'都是以这样的方式定义的:它们必须经过整个列表才能产生部分结果(实际上没有办法将它们定义为情况并非如此)。所以既不适用于无限列表。

答案 1 :(得分:5)

这些是普通foldlrepeat的定义:

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的一个棘手问题是,他们已经了解到,在这种语言中,左折叠比右折叠“更好”,因为左折叠是尾递归的,因此在恒定的空间中运行,而右侧折叠是真正的递归,并且会在长列表中将堆栈吹掉。

在Haskell中,由于懒惰,通常是相反的方式,因此foldlfoldl'是很糟糕的,而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)