在〈Haskell Tutorial(4)這裏,那裏,到處都是函式〉中談過,對於 a -> a -> a
,將最後一個當成傳回值,而前面就是參數,只是個簡單說法,多參數函式,其實是由多個單參數函式連續呼叫組成,因為這樣的特性,在其他語言中貌似高深的高階函式,在 Haskell 中根本就像喝水一樣自然的應用。
部份套用函式
多參數函式,其實是由多個單參數函式連續呼叫組成,這到底是什麼意思?舉例來說,如果你妽下定義了一個函式:
add :: Int -> Int -> Int
add x y = x + y
那麼你可以使用 add 10 20
這樣的方式來呼叫,結果會是 30
,你也可以使用 (add 10) 20
的方式來呼叫,結果也是 30
,幹嘛多此一舉地用個括號?你也可以如下方式來呼叫:
範例中 (add 10)
看似沒有提供 add
完整的引數,然而有傳回東西,你令 addTen
為 (add 10)
的傳回值,這個傳回值顯然是個函式,因為你可以使用 addTen 20
來呼叫,使用 :t
來檢驗 addTen
,確實是個可接受 Int
並傳回 Int
的函式。
這種沒有對多參數函式套用所有引數而取回的函式,稱為部份套用(Partially applied)函式,對於 add
而言,可以將型態 Int -> Int -> Int
看成或寫成是 Int -> (Int -> Int)
,也就是接受一個引數,然後傳回一個 Int -> Int
的函式,這也就是為何使用 :t
檢驗 addTen
時,結果會是 Int -> Int
的原因,addTen
的效果其實相當於:
addTen :: Int -> Int
addTen y = 10 + y
Curried 函式
對於這種在套用不齊全的引數下,會傳回函式以便後續套用引數的多參數函式,我們稱為 Curried 函式,你也許在其他主流語言中,看過它試圖用奇妙的方式來實現這個特性,然而,這在 Haskell 中,卻是多參數函式本身的組成方式。
Curried 函式的作用在於,你可以隨時隨地,依需求從既有的函式中直接產生新的函式。記得在 Haskell 中,到處都是函式嗎?就連 +
、-
、*
、/
等都是函式,知道可以幹什麼了嗎?你根本不用定義一個 add
函式,然後再用 addTen
來得到一個可以加 10
的函式,只要如下進行就可以了:
+
本身是個接受兩個引數的函式,(+ 10)
傳回一個函式,這使得 addTen
相當於以下的定義:
addTen :: Integer -> Integer
addTen x = x + 10
對於 +
、*
、/
等運算,都可以使用 (+ 10)
這樣的方式來得到新函式,-
是個例子,因為 (- 10)
這樣的表示,在 Haskell 是當作負 10
,你可以使用 subtract
來得到一個減 10
的函式,像是 subtract 10
。
高階函式
好吧!你也許也知道,如果一個函式可以接受函式作為引數,或者是傳回函式,或者兩者皆有,這種函式就被稱為高階(High order)函式,所以了,+
也可以是一個高階函式了,在其他語言中層級高一層的高階函式,在 Haskell 其實很普通。
不過,嚴格來說,這種普通只是在語法層面上簡單而已,高階函式其實通常意謂著,有一個流程模式太常見,因而將共用的流程抽取出來,不能共用需要有特定的部份,可以讓你指定程式碼。
舉例而言,你會想對一個數字清單,過濾出超過 3
的清單,你會走訪各元素,然後對各數字比對是否大於 3
,是的話收集到新的清單中,在另一個需求中,你也會想對一個數字清單,比對出是否小於 5
,你會走訪各元素,然後對各數字比對是否小於 5
,是的話收集到新的清單中 …
單是在上頭的描述中,你就會察覺得重複的文字描述了,實際上,我也只是複製然後改改幾個字,就完成第二個需求描述,也就是只將「大於 3
」改為「小於 5
」罷了,如果這個運算是可以傳遞的那麼「你也會想對一個數字清單,比對出是否 ???,你會走訪各元素,然後對各數字比對是否 ???,是的話收集到新的清單中 」就可以封裝為一個函式了。
在 Haskell 中,已經有這個函式,叫做 filter
,例如:
在 Haskell 中,>
、<
等也都是函式,(> 3)
建立了一個函式,
(< 5)也建立了一個函式,
filter的第二個參數接受函式,第三個函式接受一個清單,在 Haskell 中可以使用
[]來建立清單,
filter` 封裝了走訪各元素的流程,這樣你就可以指定比對條件了。
filter
是個高階函式,(a -> Bool)
是第一個參數接受的型態,也就是一個函式,第二個參數接受 [a]
清單,傳回值也是 [a]
清單。
組合既有的函式
如果現在還沒有清單可以用,你只是想要一個可以比對大於 3
的函式,那該怎麼辦呢?可以如下定義:
biggerThanThree :: [Int] -> [Int]
biggerThanThree xs = filter (> 3) xs
實際上,此時 xs
可以省略,直接令 biggerThanThree
為 filter (> 3)
就可以了:
biggerThanThree :: [Int] -> [Int]
biggerThanThree = filter (> 3)
這稱為 Point free 或 Pointless 風格,實際上將此風格強調出來,就是讓你在面對這類需求時,可以直接思考如何組合既有的函式,產生一個新函式來滿足需求。同樣地,在主流語言中,往往得用奇妙的設計來達成這類需求,在 Haskell 中很簡單,舉個來說,Haskell 中有個 map
,如果你有個清單 [10, 20, 30, 40, 50]
,想對每個都加上 5
後傳回新清單,那就 map (+ 5) [10, 20, 30, 40, 50]
,傳回結果就是 [15, 25, 35, 45, 55]
。
如果你只是要個可以對清單全部加 5
的函式呢?那就是組合 map
與 (+ 5)
了,map (+ 5)
就是你想要的:
這邊看到了 filter
、map
等高階函式,之後有機會還會介紹到其他的,在這篇結束之前,來出個考題好了,請試著用你目前看過的所有函式知識,實作出以下 xsMap
效果: