【问题标题】:Haskell Does Not Evaluate Lazily takeWhileHaskell 不评估懒惰的 takeWhile
【发布时间】:2016-12-14 02:34:18
【问题描述】:
isqrt :: Integer -> Integer
isqrt = floor . sqrt . fromIntegral

primes :: [Integer]
primes = sieve [2..] where
 sieve (p:ps) = p : sieve [x | x <- ps, x `mod` p > 0]

primeFactors :: Integer -> [Integer]
primeFactors n = takeWhile (< n) [x | x <- primes, n `mod` x == 0]

这是我的代码。我想你猜到了我想要做什么:使用无限素数列表的给定数的素数列表。但是这段代码不会懒惰地评估。

当我使用ghci:l mycode.hs 并输入primeFactors 24 时,结果是[2, 3(并且光标在那里不断闪烁)没有进一步的Prelude&gt; 提示。我认为那里有问题。我做错了什么?

谢谢。

【问题讨论】:

  • 请注意,列表[x | x &lt;- primes, 10 `mod` x == 0] 不是 [2,5],而是类似于2:5:infiniteLoop,因为在5 之后将尝试无限多个素数x他们可能会通过测试。当然我们知道他们不会,但列表理解不知道这一点。然后takeWhile 陷入循环。
  • @chi 是的,我知道这将是一个无限列表,谢谢。但我认为当谓词不再成立时,takeWhile 将停止评估无限列表。
  • 不。它不是一个无限的列表。在那个takeWhile 上没有问题。试试takeWhile (&lt;=10) [1..]takeWhile (&lt;=10) (1:2:5:let f n = f (n+1) in f 3)。这里[1..] 是一个无限列表,而1:2:5:... 不是——它是一个在其第三个元素之后具有未定义脊椎的列表。在无限列表中,take n list 将始终返回,而当脊椎未定义时则不会。

标签: haskell list-comprehension primes lazy-evaluation prime-factoring


【解决方案1】:

takeWhile 永远不会因复合参数而终止。如果n 是复合的,则它没有素因数&gt;= n,所以takeWhile 将坐在那里。

takeWhile 应用于素数列表,然后用n mod x 过滤结果,如下所示:

primeFactors n = [x | x <- takeWhile (<= n) primes, n `mod` x == 0]

(使用&lt;= 代替&lt; 以获得最大的正确性,因此素数的素数将由该数组成)。

【讨论】:

  • 您的代码运行良好,谢谢。但我不确定我是否理解takeWhile 的情况。它实际上在哪里停止?还是列表推导导致无穷大?
  • @D.Jones 两者兼而有之。考虑:takeWhile p xs,如果xs 是有限的,那么takeWhile p xs 也是有限的,但是如果xs 是无限的,那么takeWhile p xs 可能 是有限的还是无限的,这取决于是否存在xs 的有限前缀,其中 p 变为 false。在您的情况下,列表理解将产生一个有限前缀,其中所有元素都使 p 为真,之后不再生成任何元素但是列表理解会一直尝试素数。
  • 区分“有限”(也称为终止)列表和只有有限数量元素的列表很重要。例如。 1:2:3:[] 是有限的(终止),1:2:3:infiniteLoop 不是,尽管它只有 3 个元素。所以列表推导不会终止,即使它只产生有限数量的元素;并且takeWhile 不会终止,因为条件永远不会变成false 并且列表参数(列表理解)不会终止。
【解决方案2】:

举例说明会发生什么:

http://sketchtoy.com/67338195

【讨论】:

    【解决方案3】:

    你的问题不是直接takeWhile,而是列表理解。

    [x | x <- primes, n `mod` x == 0]
    

    对于n = 24,我们得到24 `mod` 2 == 024 `mod` 3 == 0,所以这个列表推导的值以2 : 3 : ... 开头。但请考虑... 部分。

    列表理解必须不断从primes 中提取值并检查24 `mod` x == 0。由于没有更多的主要因素 24 没有任何东西可以通过该测试并作为列表理解的第三个值发出。但由于总是有另一个质数要测试,它永远不会停止并得出结论认为列表的剩余尾部是空的。

    因为这是懒惰的评估,如果你只要求这个列表的前两个元素,那么你就可以了。但是,如果您的程序需要第三个元素(或者甚至只是想知道是否存在 第三个元素),那么列表推导式将永远旋转并试图想出一个。

    takeWhile (&lt; 24) 不断从其参数中提取元素,直到找到不是&lt; 24 的元素。 23 都通过了该测试,所以 takeWhile (&lt; 24) 确实需要知道列表解析的第三个元素是什么。

    takeWhile 并不是真正的问题;问题是您已经编写了一个列表推导来查找所有主要因素(仅此而已),然后尝试对其 results 使用过滤器来切断对所有不可能是因子的更高质数。如果你停下来想一想,那真的没有意义。根据定义,任何不是主要因素的东西都不能成为该列表的元素,因此您不能从该列表中过滤掉大于n 的非因素。相反,您需要将 input 过滤到该列表理解中,这样它就不会尝试探索无限空间,正如@n.m 的答案所示。

    【讨论】:

      最近更新 更多