为什么不映射sqrt [1 ..]不能给出无限递归? 我怎样才能更好地理解haskell?
sqrtSums :: Int
sqrtSums = length ( takeWhile (<1000) (scanl1 (+) (map sqrt[1..]))) + 1
答案 0 :(得分:2)
Haskell中的列表表现得好像它们具有内置迭代器或流接口,因为默认情况下整个语言使用延迟评估,这意味着只有在调用函数需要时才计算结果。
在您的示例中,
sqrtSums = length ( takeWhile (<1000) (scanl1 (+) (map sqrt[1..]))) + 1
好像length
不断向takeWhile
询问另一个元素,
向scanl1
询问另一个元素,
向map
询问另一个元素,
向[1..]
询问另一个元素。
一旦takeWhile
得到的内容不是<1000
,它就不会向scanl1
询问更多元素,因此[1..]
永远不会得到完全评估。
未评估的表达式称为 thunk ,从thunk中获取答案称为 reduce 。例如,thunk [1..]
首先减少到1:[2..]
。在许多编程语言中,通过编写表达式,可以强制编译器/运行时计算它,但不能在Haskell中计算。我可以写ignore x = 3
并执行ignore (1/0)
- 我会得到3而不会导致错误,因为1/0
不需要计算来生成3
- 它只是并没有出现在我想要制作的右手边。
同样,您不需要在列表中生成超过131的任何元素,因为那时总和已超过1000,而takeWhile
生成一个空列表[]
,此时{{1} }}返回length
,130
生成sqrtSums
。
答案 1 :(得分:1)
Haskell评估表达式 lazily 。这意味着仅在需要时才进行评估。在此示例中,takeWhile (< 1000)
反复要求scanl1 (+) (map sqrt [1..])
的答案,但停止后,其中一个答案超过1000
。在这个开始发生的那一刻,Haskell不再评估更多(真正无限的)列表。
我们可以通过从这个例子中删除一些部分来看到这一点
>>> takeWhile (< 10) [1..]
[1,2,3,4,5,6,7,8,9]
这里我们有一个代表无限列表的表达式([1..]
),但takeWhile
确保总表达式只需要那些那些无数的值。没有takeWhile
Haskell将尝试打印整个无限列表
>>> [1..]
[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24Interrupted.
但是我们再一次注意到Haskell只需要每个元素一个接一个,因为它需要它们才能打印出来。在一种严格的语言中,我们会在打印第一个答案之前试图在内部表示无限列表。