今天要來介紹一個在 FP 中相當常見的特性 lazy evaluation (惰性求值) ,簡單來說就是當需要值的時候才會真正把值計算出來。
我們先來想一個問題,這段程式碼會走訪幾次這個 List?
map (^2) $ map (^2) $ [1..10]
按照我們直覺應該會想我先 map
一次產生 [1,4,9,16,25,36,49,64,81,100]
然後再 map
變成結果
[1,16,81,256,625,1296,2401,4096,6561,10000]
。
但這個答案是「只會走訪一次」,這歸功於 lazy evaluation 這個特性,因為這是在走訪到個別元素時才會真正使用 map
裡的 expression 去計算出值。
那 lazy evaluation的好處是什麼?當然顯而易見就是這樣**「效能比較好」**畢竟只走訪一次,但對我來說更大的好處是也連帶的讓我們不必為了效能而去讓很多複雜運算 map
刻意放在同一個 map
裡,我覺得這點對可讀性幫助很大,在寫 JS 時總會為了減少 map 次數而講每次 map
裡的 function 愈寫愈大,而在 Haskell 裡這就不會是問題了。
lazy evaluation 是也非常適合來操作無限 List
take 1000 $ map (^2) $ [1..]
這是一個對於一個無限List中的每個元素都平方,最後只取前一千個元素的 expression ,在沒有 lazy evaluation 我們根本達不到這件事情。因為既然是無限 List 那根本沒有執行完的一天,但在 Haskell 我們可以用 take
、takeWhile
甚至是 head
、pattern matching 等等你想得到可以對 List 操作的方法來處理無限 List(當然 tail
就沒辦法了xD)
take 10 $ filter (>1000) [1..] -- [1001,1002,1003,1004,1005,1006,1007,1008,1009,1010]
takeWhile (<= 1000) $ map (^3) [2,4..] -- [8,64,216,512,1000]
[1..] !! 3000 -- 3001
head $ map (^2) $ map (+2) $ map (+2) [1..] -- 25
今天寫得十分的趕QQ
但希望有成功的把 lazy evaluation 介紹到一個程度