我可以添加一些關於GHC優化系統的重要細節。
foldr
的天真定義傳遞函數。在調用一個函數時有一個固有的開銷 - 特別是當這個函數在編譯時不知道的時候。如果能夠在編譯時知道函數的定義,那將非常好。
有一些技巧可用於在GHC中執行內聯 - 這是他們的一個例子。首先,foldr
需要內聯(我會後來爲什麼)。 foldr
的幼稚實現是遞歸的,所以不能被內聯。所以一個worker/wrapper轉換被應用於定義。工作人員是遞歸的,但包裝不是。這允許foldr
內聯,儘管在列表結構上遞歸。
當foldr
被內聯時,它也會創建其所有本地綁定的副本。它或多或少是一個直接的文本內聯(模仿一些重命名,並在脫嫁後發生)。這是事情變得有趣的地方。 go
是一個本地綁定,優化器可以查看它的內部。它注意到它在本地範圍內調用一個函數,它命名爲k
。 GHC通常會完全刪除k
變量,並將其替換爲表達式k
簡化爲。然後,如果函數應用程序可以內聯,則可以在此時內聯 - 消除完全調用第一類函數的開銷。
我們來看一個簡單而具體的例子。此程序將回聲一個輸入行與所有尾隨'x'
字符刪除:
dropR :: Char -> String -> String
dropR x r = if x == 'x' && null r then "" else x : r
main :: IO()
main = do
s <- getLine
putStrLn $ foldr dropR "" s
首先,優化器將內聯foldr
的定義和簡化,從而導致代碼看起來是這樣的:
main :: IO()
main = do
s <- getLine
-- I'm changing the where clause to a let expression for the sake of readability
putStrLn $ let { go [] = ""; go (x:xs) = dropR x (go xs) } in go s
這就是工作者包裝轉換允許的事情。我將跳過其餘的步驟,但很明顯GHC現在可以內聯dropR
的定義,消除函數調用開銷。這是贏得巨大勝利的地方。
在其他的答案沒有提及
尚未提及的一個細節:ghc只在函數完全應用時內聯,*語法*在其左側。如果你習慣於考慮捲曲和創建漂亮的無點式代碼,這是非常奇怪和醜陋的。這就是爲什麼你有時會在優化代碼中看到'='右邊的愚蠢lambda表達式。 – jberryman 2014-10-07 21:07:34