我正在学习Haskell和Haskell Wiki上的以下表达式 我真的很困惑:
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
我无法弄清楚为什么会有效。
如果我应用标准Currying逻辑(zipWith (+))
,则返回一个函数将列表作为参数,然后返回另一个函数,该函数将另一个列表作为参数,并返回一个列表(zipWith::(a -> b -> c) -> [a] -> [b] -> [c]
)。因此,fibs
是对列表的引用(尚未评估),(tail fibs)
是相同(未评估)列表的尾部。当我们尝试评估(take 10 fibs
)时,前两个元素绑定到0
和1
。换句话说,fibs==[0,1,?,?...]
和(tail fibs)==[1,?,?,?]
。第一次添加完成后,fibs
变为[0,1,0+1,?,..]
。同样,在第二次添加后,我们得到[0,1,0+1,1+(0+1),?,?..]
fibs !! 4
时会发生什么评估?EDIT2:我刚刚发现了上述问题并得到了很好的回答here。如果我浪费了任何人的时间,我很抱歉。
编辑:这是一个更难理解的案例(来源:Project Euler forums):
filterAbort :: (a -> Bool) -> [a] -> [a]
filterAbort p (x:xs) = if p x then x : filterAbort p xs else []
main :: Int
main = primelist !! 10000
where primelist = 2 : 3 : 5 : [ x | x <- [7..], odd x, all (\y -> x `mod` y /= 0) (filterAbort (<= (ceiling (sqrt (fromIntegral x)))) primelist) ]
注意all (\y -> x mod y /= 0)...
这里如何引用x不会导致无限递归?
答案 0 :(得分:15)
我将为您追踪评估:
> fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
使用:
> zipWith f (a:as) (b:bs) = f a b : zipWith f as bs
> zipWith _ _ _ = []
> tail (_:xs) = xs
> tail [] = error "tail"
所以:
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
↪ fibs = 0 : 1 : ((+) 0 1 : zipWith (+) (tail fibs) (tail (tail fibs)))
↪ fibs = 0 : 1 : 1 : ((+) 1 1 : zipWith (+) (tail (tail fibs)) (taii (tail (tail fibs)))))
↪ fibs = 0 : 1 : 1 : 2 : ((+) 1 2 : zipWith (+) (tail (tail (tail fibs))) (tail (taii (tail (tail fibs))))))
↪ fibs = 0 : 1 : 1 : 2 : 3 : ((+) 2 3 : zipWith (+) (tail (tail (tail (tail fibs)))) (tail (tail (taii (tail (tail fibs)))))))
等。因此,如果您索引此结构,它将强制评估每个fibs步骤,直到找到索引。
为什么效率这么高?一句话:分享。
计算fibs
时,它会在堆中增长,记录已经是计算机的值。稍后对fibs
的引用将重用fibs
的所有先前计算的值。免费记忆!
堆中的共享是什么样的?
当您在列表末尾请求对象时,Haskell计算下一个值,记录它,并将该自引用移动到节点下。
这终止的事实主要取决于懒惰。