我在读LYAH时遇到了一个问题。
以下是我对无限列表中的foldr的看法:
foldr (:) [] [1..] = 1:2:...:**∞:[]**
我认为GHCi在评估∞:[] 之前并不知道它是列表。
但GHCi知道。
所以我认为它可以识别foldr(:) [] [无限列表] = [无限列表本身]。
Prelude> [1..10] == (take 10 $ foldr (:) [] [1..])
True
Prelude> [1..] == (foldr (:) [] [1..])
Interrupted.
然而事实并非如此。
我想知道在评估∞:[]之前GHCi识别它是[1 ..]时实际发生了什么。
只需在评估之前输入推理?
答案 0 :(得分:5)
我想知道在评估
[1..]
之前GHCi识别∞:[]
时实际发生了什么。
GHCi 不认识到它是[1...]
,这只是 lazy 评估的结果。
foldr
实现为:
foldr _ z [] = z
foldr f z (x:xs) = f x (foldr f z xs)
如果你写的是foldr (:) [] [1..]
之类的东西,那么Haskell会不宣传这个(直接),它只存储你想要计算的东西。
现在说你想要print (take 3 (foldr (:) [] [1..]))
那个列表,然后Haskell被迫评估它,它将通过计算来实现:
take 3 (foldr (:) [] [1..])
-> take 3 ((:) 1 (foldr (:) [] [2..]))
-> (:) 1 (take 2 (foldr (:) [] [2..]))
-> (:) 1 (take 2 ((:) 2 (foldr (:) [] [3..]))
-> (:) 1 ((:) 2 (take 1 (foldr (:) [] [3..])))
-> (:) 1 ((:) 2 (take 1 ((:) 3 (foldr (:) [] [4..]))))
-> (:) 1 ((:) 2 ((:) 3 (take 0 (foldr (:) [] [4..]))))
-> (:) 1 ((:) 2 ((:) 3 [])
因此它派生[1, 2, 3]
,并且由于Haskell的懒惰,它对foldr (:) [] [4..]
的内容不感兴趣。即使该列表最终会停止,也不会对其进行评估。
如果计算[1..] = foldr (:) [] [1..]
之类的东西,那么Haskell将检查列表相等性,列表相等性定义为:
[] == [] = True
(x:xs) == (y:ys) = x == y && xs == ys
[] == (_:_) = False
(_:_) == [] = False
所以Haskell被迫展开右侧foldr
的列表,但它会继续这样做,直到它找到不相等的项目,或者列表中的一个到达结束。但是因为每次元素 相等,并且两个列表都永远不会结束,所以它永远不会完成,因为它会像下面那样评估它:
(==) [1..] (foldr (:) [] [1..])
-> (==) ((:) 1 [2..]) ((:) 1 (foldr (:) [] [2..]))
它看到两者都是相同的,所以它递归地调用:
-> (==) ((:) 1 [2..]) ((:) 1 (foldr (:) [] [2..]))
-> (==) [2..] foldr (:) [] [2..])
-> (==) ((:) 2 [3..]) ((:) 2 (foldr (:) [] [3..]))
-> (==) [3..] foldr (:) [] [3..])
-> ...
但正如你所看到的,它永远不会停止评估。 Haskell不知道 foldr (:) [] [1..]
等于[1..]
,它的目的是评估它,并且由于等式迫使它评估整个列表,它将陷入无限循环。
是的,可以在编译器中添加某个模式,这样foldr (:) [] x
将替换为x
,因此将来也许Haskell编译器可以返回True
这些,但这不能解决问题从根本上,因为如果Haskell可以为任何类型的函数(这里(:)
)派生这样的东西,那么它将解决一个不可判定的问题,因此它不是可能的)。
答案 1 :(得分:2)
Ghc(至少在理论上)不知道有限列表和无限列表之间的区别。它可以通过计算列表长度来判断列表是否有限。如果你试图找到一个无限列表的长度,你将会遇到一个糟糕的时间,因为你的程序永远不会终止。
这个问题实际上是关于懒惰的评价。在像C或python这样严格的语言中,你需要知道每一步的整体价值。如果你想要列出一个列表的元素,你需要知道它的内容和开始之前有多少。
Haskell中的所有数据都具有以下形式:
True
,Left 7
,(,) 5 'f'
(与(5,'f')
相同)或(:) 3 []
但在Haskell中,值有两个“形状”
在Haskell中有一个名为弱头正常形式的概念,其中:
让我们看一下foldr (:) [] [1..]
的评估过程。首先是foldr
foldr f a [] = a
foldr f a (x:xs) = f x (foldr xs)
现在什么是foldr (:) [] [1..]
?
foldr (:) [] [1..]
这似乎只是一个笨蛋。我们对此一无所知。所以让我们把它评估成WHNF。首先,我们需要将参数[1..]
(实际上是enumFrom 1
)转换为WHNF,以便我们可以对其进行模式匹配:
foldr (:) [] (1:[2..])
现在我们可以评估foldr:
(:) 1 (foldr [] [2..])
1 : (foldr [] [2..])
因此,我们计算了列表的第一个元素,而不必查看其整个无限长度。同样,我们可以计算出第二个元素,依此类推。
那么如果我们[1..] == [1..]
会怎么样?那么列表==
的定义是(省略三种情况)
(x:xs) == (y:ys) = x == y && xs == ys
因此,我们试图减少到WHNF:
[1..] == [1..]
(1 == 1) && ([2..] == [2..])
True && ([2..] == [2..])
[2..] == [2..]
... and so on
因此,我们继续前进,永远不会得到一个构造函数,我们可以用它来模拟匹配(即检查)结果。
请注意,我们可以取消True && ...
,因为&&
的定义不会查看其第二个参数:
True && x = x
False && _ = False
如果我们用一个完整的四向真值表定义&&
,那么程序的内存可能会快得多(假设编译器没有做任何聪明的事情)而不是上面的内容,相反,你将会用尽耐心(或宇宙射击击中你的ram并让你的程序返回False
)