今天的標題一長串都是英文,看起來怪恐怖的,不過其實這三個名詞應該都還算是好理解的概念,他們算是構築 Functional Programming 風格的基本原則,讓我們繼續看下去。
Currying
currying 中文翻作柯里化,不是煮咖哩的意思。為了紀念數學家 Haskell Brooks Curry 在組合子理論的貢獻,所以特別把這個操作以他的名字紀念。順帶一提,知道 Haskell 為什麼要叫 Haskell 了嗎?
一句話解釋 currying: 將多參數的函數變成多個單一參數的函數的組合。
考量下面的 JavaScript 程式碼:
function sum(x, y, z) {
return x + y + z
}
sum(1, 2, 3) // 6
是一個吃三個參數並回傳總和的一個樸實無華的函數。
若我們對他做 currying則會變成:
function sum(x){
return function(y){
return function(z) {
return x + y + z
}
}
}
sum(1)(2)(3) // 6
誒,怎麼樸實無華的函數瞬間變得這麼複雜,我們改用 arrow function 來改寫看看:
const sum= x => y => z => x + y + z
sum(1)(2)(3) // 6
好多了。
所以 currying 有什麼好處?寫成這樣看起來可讀性變差了,用起來好像也沒有比較簡單,為什麼要搬石頭砸自己的腳呢?
我們把這問題留到後面兩個念講完再回頭想。
Pointfree
pointfree 是一種程式設計風格,即寫函數定義的時候,忽略函數的參數,舉一個 JS 的例子來說:
const square = n => n ** 2
// 1, 4, 9, 16
[1, 2, 3, 4].map(n => square(n))
我們可以注意到, n => square(n)
實際上也是一個函數,但他的功用只有呼叫另一個函數而已,所以我們可以把這段 code 改寫成:
const square = n => n ** 2
// 1, 4, 9, 16
[1, 2, 3, 4].map(square)
這樣子看起來是不是比原本的寫法簡潔易懂不少呢?這就是 pointfree 的概念,盡量把參數省略掉,參數長什麼樣子都無所謂。
但是參數不可能長什麼樣都無所謂啊,有函數吃三個參數,有函數吃四個參數,這樣要怎麼 pointfree?如果大家都只有一個參數就好了......誒等等,我們剛剛是不是有提一個風格可以讓大家都只吃一個參數?沒錯!這也是為什麼要做 currying 的一部分原因:為了搭配 pointfree 風格。當我們用 currying 保證每個函數都只吃一個參數,就可以很容易地用 pointfree 風格來寫程式。
Higher Order Function
higher order function 中文譯作高階函數,常縮寫為 HOF ,如果是寫 react 的朋友可能聽過 HOC(higher order component),便是借鏡於這個概念。
高階函數聽起來好像很厲害,但實際上他的定義十分簡單:回傳函數的函數,或是接受函數作為參數的函數。
以前面做完 curring 的 add 函數來說:
const add = x => y => z => x + y + z
add(1)(2)(3) // 6
add 就屬於一個 HOF ,他回傳另一個函數 y => z => x + y + z
。其他像是 js 的 Array.protoype.map
, Array.prototype.filter
,是吃一個函數作為參數,回傳一個陣列,因此也屬於 HOF。
HOF 有一個功能是用來增強 (enhance) 作為參數的函數,如 Array.prototype.map
就是把傳進的函數應用在整個陣列之上,從 OOP 的角度有一點像是繼承的感覺,但是又比繼承更加的靈活。透過函數的組合(composition)可以變化出很多不同的邏輯。
這裡要引入一個很重要的 HOF: compose
。在許多 FP 的 library 裡都會實作這樣子的 HOF。 compose
的功能就是把多個函數的輸入輸出串接在一起,假設我有函數 f
、函數 g
跟函數 h
,則考量下列 JavaScript 程式碼:
const func = (...args) => f(g(h(...args)))
// 等價於
const func = compose(f, g, h)
可以明顯發現用了 compose
HOF 之後,我們可以更容易做到 pointfree 風格,寫出更簡潔且更具表達性的程式碼。
這個短碼中始用 ...args
是考量到 h
可能吃不止一個參數,但假如我們規定 h
是一個單參數的函數 — 也就是經過 curring 之後的話,就會發現 f(g(h(x)))
中的所有函數都是吃一個參數的函數。也就是透過單參數的函數組合,就可以自由又有彈性的實現很多複雜的邏輯,並且組合這些邏輯的函數通通都是可以複用的!
總結
今天所說的三個概念:currying、pointfree、higher order function,乍看之下好像很難,其實都是滿單純的概念。懂的搭配使用的話,就能用簡潔的方式靈活組合出複雜的邏輯,這也是 FP 的優勢之一。