6 摺

第2 章中的許多歸納函數定義都循著同一個固定模式。 以 sum, length, 與 map f 為例:

sum :: List Int -> Int
sum []      = {-"{\color{burntorange}"-}0{-"}"-}
sum (x:xs)  = {-"{\color{burntorange}"-}x  +{-"}\,"-} sum xs 

length :: List a -> Nat
length []      = {-"{\color{burntorange}"-}Zero{-"}"-}
length (x:xs)  = {-"{\color{burntorange}"-}Suc{-"}"-} (length xs) 

map :: (a -> b) -> List a -> List b
map f []      = {-"{\color{burntorange}"-}[]{-"}"-}
map f (x:xs)  = {-"{\color{burntorange}"-}f x  :{-"\,}"-} map f xs 

它們都在輸入為 [] 時傳回某個基底值,在輸入為 x:xs 時在 xs 上遞迴呼叫, 並將呼叫結果稍作加工。 三者的不同之處只在橘色的部分,即基底值以及用於加工的函數:sum 使用 0 與 (+), length 使用 Zero 與 Suc, map f 則使用 [] 與 (f x :). 如果說「抽象化」是一個高階程式語言給我們的最重要能力,我們能否將這個模式抽象出來呢?

我們把上述三個定義中橘色的部分抽出變成參數,將餘下的函數稱為 foldr:

foldr :: (a -> b -> b) -> b -> List a -> b
foldr f e []      = e
foldr f e (x:xs)  = f x (foldr f e xs) 

如此一來,sum, length, 與 map f 都是 foldr 的特例:

sum     = foldr (+) 0 
length  = foldr (\x n -> Suc n) Zero 
map f   = foldr (\x ys -> f x : ys) [] 

函數 foldr 是串列上的「摺(fold)」 --- foldr 一詞是 fold 與「右邊(right)」的縮寫,意謂該函數是一個往右結合的摺。我們將在下一節解釋。