【问题标题】:Understanding Haskell lazy evaluation [duplicate]了解 Haskell 惰性求值 [重复]
【发布时间】:2014-11-13 07:23:00
【问题描述】:
请原谅我的愚蠢问题,我是 Haskell 的新手。
我在 Haskell 中尝试了以下方法:
sum [fib n| n <- [1..], (even (fib n) && fib n < 4000000)]
这需要无限的时间。如果我忽略n <- [1..],解决方案马上就来了。
我认为这应该无关紧要,因为 Haskell 正在评估惰性。我是否误解了惰性评估?
【问题讨论】:
标签:
haskell
lazy-evaluation
fibonacci
【解决方案1】:
注意
sum [ n | n <- [1..], n < 10 ]
也不会终止,因为它会尝试所有可能的ns,以防发现还有一个元素“小于 10”,以便将其包含在总和中。
相比之下,
sum $ takeWhile (< 10) [ n | n <- [1..] ]
将终止,因为一旦发现不满足谓词<10 的项目,takeWhile 将丢弃列表的其余部分。
【解决方案2】:
你对列表的理解
sum [fib n | n <- [1..], even (fib n) && fib n < 4000000]
等价于表达式
sum $ map fib $ filter (\n -> even (fib n) && fib n < 4000000) [1..]
看filter的定义:
filter :: (a -> Bool) -> [a] -> [a]
filter predicate [] = []
filter predicate (x:xs)
| predicate x = x : filter predicate xs
| otherwise = filter predicate xs
我们可以看到它总是会检查列表中的每个元素,直到它到达列表的末尾。提供给表达式过滤的列表是[1..],它是无限的。这在 Haskell 中很好,它只是意味着如果你强制评估整个列表,过滤器将永远不会完成。然后你将它传递给map fib,它也可以很好地处理无限列表,但是你将它传递给sum,这需要有限数量的元素相加。
要解决此问题,正如@chi 所指出的,您可以改用takeWhile:
sum $ map fib $ filter (\n -> even (fib n)) $ takeWhile (\n -> fib n < 4000000) [1..]
虽然我会注意到您在此表达式中应用了 fib 3 个不同的时间。最好是先map fib,然后就不用再申请了:
sum $ filter even $ takeWhile (< 4000000) $ map fib [1..]