为啥这个 Haskell 代码可以成功地处理无限列表?

Posted

技术标签:

【中文标题】为啥这个 Haskell 代码可以成功地处理无限列表?【英文标题】:Why does this Haskell code work successfully with infinite lists?为什么这个 Haskell 代码可以成功地处理无限列表? 【发布时间】:2010-10-24 09:47:36 【问题描述】:

我有一些 Haskell 代码确实在无限列表上正常工作,但我不明白 为什么它可以成功。 (我修改了我的原始代码 - 没有处理无限列表 - 以合并来自其他在线代码的内容,突然我发现它可以工作但不知道为什么)。

myAny :: (a -> Bool) -> [a] -> Bool
myAny p list = foldr step False list
   where
      step item acc = p item || acc

我对 foldr 的理解是它将遍历列表中的每个项目(也许这种理解是不完整的)。如果是这样,“step”函数的措辞应该无关紧要......代码应该无法处理无限循环。

但是,以下工作:

*Main Data.List> myAny even [1..]
True

请帮助我理解:为什么??

【问题讨论】:

【参考方案1】:

让我们对 Haskell 将如何评估您的表达式进行一些跟踪。在每一行用equals代替equals,表达式很快就会得到True:

myAny even [1..]
foldr step False [1..]
step 1 (foldr step False [2..])
even 1 || (foldr step False [2..])
False  || (foldr step False [2..])
foldr step False [2..]
step 2 (foldr step False [3..])
even 2 || (foldr step False [3..])
True   || (foldr step false [3..])
True

这是因为 acc 作为未评估的 thunk(延迟评估)传递,还因为 || 函数在其 first 参数中是严格的。

这样就结束了:

True || and (repeat True)

但这不是:

and (repeat True) || True

看||的定义看看为什么会这样:

True  || _ =  True
False || x =  x

【讨论】:

此外,您可以验证代码不计算超过 2 个元素:myAny p list = foldr (\i a -> trace (show i) (p i || a)) - 将仅显示 1 2 True 哇,这是一个非常令人大开眼界的回应。首先,我并没有从我面前的foldr的定义开始。我认为它的代码会使用我还不知道的高级功能,所以我只是把它作为最后的手段。您的回答促使我看一看,这澄清了很多。 foldr 本身正在使用“普通的旧”结构递归。我喜欢你打破它的方式。谢谢。 顺便说一句,是||在它的第一个参数中完全严格,或者它只是“优先考虑”它的第一个参数?例如,如果 arg 2 已经被评估,但 arg 1 仍然只是一个 thunk 怎么办?并说 arg 2 是错误的。 Haskell会朝相反的方向短路吗?谢谢。 是的,我相信 ||在第一个参数中是完全严格的。如果至少在一侧没有完全评估的 Bool ,就无法评估对它的调用。看看 || 的来源:True || _ = 真假|| x = x 如果你想要一个“对称”||如果 either 一方为 True,它将终止,请查看 Hackage 上的 lub 包。【参考方案2】:

我对 foldr 的理解是 将循环遍历 列表(也许是那种理解 不完整)。

foldr(与foldl 不同)不必遍历列表中的每一项。看看foldr 是如何定义的会很有启发意义。

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

当评估对foldr 的调用时,它会强制评估对函数f 的调用。但请注意对foldr 的递归调用是如何嵌入到函数f 的参数中的。如果 f 不评估其第二个参数,则不会评估该递归调用。

【讨论】:

好点。我最初不知道 foldr 的定义在我的 Haskell 知识水平上是可以理解的。我注意到您如何指出递归调用被有意嵌入到函数参数中,以使其成为 thunk。这是 Haskell 开发人员发现自己经常做的事情吗?是否存在不需要函数但创建一个函数只是为了传递参数并知道它将是一个 thunk 的情况? Charlie,因为我们使用的是 Haskell,所以除非有什么东西强迫它,否则 nothing 会被评估。【参考方案3】:

我不知道 Haskell,但我怀疑在你的情况下,它的工作原理是因为懒惰的评估。因为它允许您使用无限长的列表,所以当您访问它时,它会根据您的需要计算结果。

见http://en.wikipedia.org/wiki/Lazy_evaluation

【讨论】:

【参考方案4】:

这里的关键是 Haskell 是一种非严格的语言。 “非严格”意味着它允许非严格函数,这反过来意味着函数参数在使用之前可能不会被完全评估。这显然允许延迟评估,这是“一种延迟计算直到需要结果的技术”。

从this Wiki article开始

【讨论】:

好的,我知道惰性求值。但我需要帮助将这些点与上述代码的工作原理联系起来。同时,我将查看 wiki 文章。谢谢。

以上是关于为啥这个 Haskell 代码可以成功地处理无限列表?的主要内容,如果未能解决你的问题,请参考以下文章

为啥这个 Haskell 程序表现如此糟糕?

为啥这个 Haskell 代码使用fundeps 进行类型检查,但对类型族产生不可触碰的错误?

seq如何评估Haskell中的无限列表?

为啥 Haskell 中没有隐式并行性?

如何通过 Haskell 中的弱指针缓存构建具有重复消除的无限树

Haskell中无限列表的执行部分?