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

Posted

技术标签:

【中文标题】Haskell中无限列表的执行部分?【英文标题】:Execution part of an infinite list in Haskell? 【发布时间】:2018-04-27 00:08:15 【问题描述】:

我对 Haskell 执行在无限列表上的确切工作方式有点困惑。例如,考虑下面的sum 函数:

sum (takeWhile (<10000) (filter odd (map (^2) [1..])))

这里是查找小于10000 的所有奇数平方和的代码,我知道这些takewhilefiltermap 函数是如何工作的。我的疑问是,这里map 函数从无限列表中取出一个元素并将其平方并将平方元素列表返回给filter 函数,对吗?在那种情况下,它将无限运行以对无限的元素列表进行平方,不是吗?还是只需要一个元素对其进行平方然后返回filter

【问题讨论】:

Haskell 有无限列表这一事实可能意味着可以用它们做某事,对吧?否则它们将毫无用处。 【参考方案1】:

takeWhile (&lt;10000) 限制了要为总和处理多少元素。 Haskell 是惰性求值的,只会根据需要做尽可能多的工作。出于这个原因,创建无限列表并通过 take 操作限制它是一种常见的模式。这意味着仅生成和处理列表中使用的元素。一旦takeWhiles 条件为假,则不会使用列表中的其他元素,因此不会生成它们。

【讨论】:

【参考方案2】:

Haskell 尽可能地懒惰。它不会计算任何你不要求它的值,如果你这样做let nums = filter odd (map (^2) [1..]),你还没有强迫它计算任何东西。现在它知道numsNum a =&gt; [a] 类型(因为你描述的操作的类型),但它不知道关于它的任何其他信息(这很好!)

即使您运行takeWhile (&lt;10000),您也不会强制输入任何数字。现在 ghc 知道 takeWhile (&lt;10000) nums 的类型为 (Ord a, Num a) =&gt; [a](当您与 (&lt;) 进行比较时,您已经引入了额外的类型类),但这就是它所知道的全部。

即使一旦您致电sum,ghc 就无需执行任何操作。 sum 期望 Num a =&gt; [a](技术上是 (Num a, Foldable t) =&gt; t a,但我们现在假设它们是相同的)并且你已经给了它一个。在您真正询问该操作的结果之前,ghc 不会执行任何操作。您可以通过在您的解释器中执行 let foobar = sum [1..] 来测试这一点。只要你从不询问foobar 的结果,Haskell 就可以使用该表达式。

但是,如果您要求该结果,则您已强制计算整行。 sum 需要它的列表,所以它向 takeWhile (&lt;10000) 询问它。 takeWhile 需要它的列表,所以它向 filter odd 询问它。 filter 需要它的列表,所以它向 map (^2) 询问它,但 filter 一次不需要整个列表,所以 map 仍然尽可能懒惰地工作,并且一次给每个数字一个。

一旦takeWhile 找到了一个数字(&gt;=10000),它就不再需要它并愉快地交给sum,这会产生你的结果,而 ghc 又回到了懒惰状态,不再产生@987654345 的任何结果@ 除非你需要它们。

【讨论】:

这么聪明的懒人语言。我喜欢它。谢谢@Adam 很好的解释。它也适用于列表理解sum (takeWhile (&lt;10000) [x^2 | x &lt;- [1..], odd (x^2)]),但sum [x^2 | x &lt;- [1..], odd (x^2), x^2 &lt; 10000] 的评估失败。【参考方案3】:

你可以想象函数之间的对话。

GHCi 要求sum 给出结果。 sum 醒来并要求 takeWhile 提供至少一个元素。唤醒takeWhile 并要求filter 提供至少一个元素。唤醒filter 并要求map 提供至少一个元素。唤醒 map 并要求 [1..] 提供至少一个元素。

[1..] 给出1map 应用 (^2) 并给出 (1^2)filter 检查odd (1^2) 并给出1takeWhile 检查 (1 1。 sum1 添加到累加器并继续要求takeWhile 提供至少一个元素。以此类推,直到takeWhile 检查成功。然后sum将累加器返回给GHCi。

【讨论】:

以上是关于Haskell中无限列表的执行部分?的主要内容,如果未能解决你的问题,请参考以下文章

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

haskell中的循环列表和无限列表有啥区别?

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

在 Haskell 中交错列表列表

Haskell-将两个列表放入一个元组列表中

Haskell 中的列表是归纳的还是归纳的?