我有這個相當簡單的函數來計算一個大名單的元素的平均值,使用兩個蓄能器至今持有的總和,到目前爲止計數:Haskell中的懶惰和尾遞歸,爲什麼會崩潰?
mean = go 0 0
where
go s l [] = s/fromIntegral l
go s l (x:xs) = go (s+x) (l+1) xs
main = do
putStrLn (show (mean [0..10000000]))
現在,在嚴格的語言,這將是尾遞歸的,並且沒有問題。然而,由於Haskell是懶惰的,我的谷歌搜索讓我明白,(s + x)和(l + 1)將作爲thunk傳遞給遞歸。所以這整個事情的崩潰和燒傷:
Stack space overflow: current size 8388608 bytes.
進一步谷歌搜索後,我發現seq
和$!
。這似乎是我不明白的,因爲我在這方面的所有嘗試都是徒勞的,錯誤信息是關於無限類型的。
終於讓我找到-XBangPatterns
,解決了這一切改變的遞歸調用:
go !s !l (x:xs) = go (s+x) (l+1) xs
但我不是很滿意這一點,因爲-XBangPatterns
當前的延伸。我想知道如何在不使用-XBangPatterns
的情況下進行嚴格的評估。 (!也許學的東西太多)
只要你明白我缺乏瞭解,這裏是我試了一下(唯一嘗試編譯,這是):
go s l (x:xs) = go (seq s (s+x)) (seq l (l+1)) xs
從我能理解,以次應該在這裏強制評估s和l的論點,從而避免thunk造成的問題。但是我仍然遇到堆棧溢出。
好東西。很高興知道GHC優化,並感謝 作爲本書的鏈接,看起來像一個很好的資源。 但是,當我看着某事的帖子時,它讓我覺得對我來說 看起來像使用seq應該打破尾遞歸。seq 必須在遞歸調用之後進行評估去,已經被 評估過了,所以從我對尾遞歸的理解來看,應該不再是尾遞歸了,因此就吹了堆棧。 當然不會發生,所以這裏發生了一些事情。 Haskell特別對待seq嗎?或者我只是簡單地對 尾遞歸感到困惑? – Hisnessness 2009-10-24 21:41:00
seq在運行時不存在。這只是暗示使用不同的評估策略。你會得到完全不同的代碼生成。 想象它更像是一個{ - #STRICT_WHNF# - } pragma – 2009-10-24 21:43:13