为什么右折叠可以在Haskell中处理无限列表?

时间:2016-02-19 05:54:14

标签: list haskell infinite fold

对于无限列表,它没有“最后”元素。那么foldr怎么能用呢?

我有来自Haskell书籍的这段代码片段:

(&&)::Bool->Bool->Bool
True && x = x
False && _ = False

and' :: [Bool]->Bool
and' xs=foldr (Main.&&) True xs

然后在Prelude中加载此hs文件并运行:

*Main> and' (repeat False)
False

它按预期工作,但我不明白:

  1. (&&)的定义在其左侧接收布尔值,    但我们应用True && x = x,而变量x位于其右侧。    这不奇怪吗?
  2. foldr返回(&&)时,为什么False会停止?    在我的理解中,foldr将从尾部到头部循环遍历列表。    是否有任何内部“休息”机制?
  3. foldr从列表的最后一个元素开始,但是无限列表没有结束。    foldr如何开始工作?

2 个答案:

答案 0 :(得分:2)

您需要熟悉一下语法。 在Haskell中,函数定义如下:

 this   =   that

并且,基本上,它意味着:当您看到时,它意味着 。事实上

True && x 

x

相同
False && x 

False,这就是为什么我们可以像你的例子那样编写定义。

对于你的第二个问题:foldr"停止"的情况并非如此。 &&运算符不评估其右操作数,左边的操作数为False

对于第3个问题:不,foldr不是从列表的最后一个元素开始。请看一下foldr的定义:

foldr f z [] = z
foldr f z (x:xs) = x `f`  foldr f z xs

现在假设

foldr (&&) True (False:xs)

评估为

False && foldr (&&) True xs

从那以后

False && _

False,结果是False

答案 1 :(得分:1)

让我们重复你的定义并添加更多:

(&&)::Bool->Bool->Bool
True && x = x
False && _ = False

and :: [Bool]->Bool
and xs = foldr (&&) True xs

repeat :: a -> [a]
repeat a = a : repeat a

foldr :: (a -> r -> r) -> r -> [a] -> r
foldr _ z [] = z
foldr f z (a:as) = f a (foldr f z as)

现在,我们可以通过手动评估来证明这一点,注意“懒洋洋地”(首先是最外面的应用程序,并且只评估足以解析最外面的数据构造函数):

and (repeat False)
  = foldr (&&) True (repeat False)           -- definition of `and`
  = foldr (&&) True (False : repeat False)   -- definition of `repeat`
  = False && foldr (&&) True (repeat False)  -- `foldr`, second equation
  = False                                    -- `&&`, second equation

关键是&&定义的第二个等式丢弃了它的第二个参数:

False && _ = False

这意味着,在运行时术语中,我们从不会在遇到foldr的步骤中强制False递归调用。

另一种看待它的方法是考虑foldr的第二个等式,以及当我们进行懒惰评估时它意味着什么:

foldr f z (a:as) = f a (foldr f z as)

由于对foldr的递归调用作为f的参数发生,这意味着,在运行时,函数f决定其第二个参数的值是否必要,所以在折叠的每一步选择是否递减列表。而这个“决定”过程从左到右进行。

  

在我的理解中,foldr将从尾部到头部循环遍历列表。是否有任何内部“休息”机制?

严格地说,在纯函数式语言中,没有内在的评估顺序概念。表达式可以按照与其数据依赖性一致的任何顺序进行评估。

你在这里所说的是一个常见的误解,即那些从不纯洁,热切的语言中学习foldr的人会转移到Haskell。用一种非常有用的经验法则,但在Haskell中,通过纯粹的懒惰评估,这条规则只会让你感到困惑。在编写Haskell时,相反的经验法则通常很有用:foldr将从左到右访问列表元素,并且在每一步它的f参数函数来决定是否列表的其余部分是必要的。

这方面的一个极端例子是使用foldr实现一个函数来获取列表的头部:

-- | Return `Just` the first element of the list, or `Nothing` if the
-- list is empty.
safeHead :: [a] -> Maybe a
safeHead = foldr (\a _ -> Just a) Nothing

例如:

safeHead [1..]
  = foldr (\a _ -> Just a) Nothing [1..]
  = foldr (\a _ -> Just a) Nothing (1:[2..])
  = (\a _ -> Just a) 1 (foldr (\a _ -> Just a) Nothing [2..])
  = Just 1