[Day04] Currying, Pointfree, Higher Order Function


今天的標題一長串都是英文,看起來怪恐怖的,不過其實這三個名詞應該都還算是好理解的概念,他們算是構築 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 的優勢之一。

#Functional Programming #程式設計





Functional Programming 到底是什麼?為什麼最近這麼夯? 這一系列的文章會探討幾個 FP 的特性,讓大家一起體驗 FP 的魔力。

留言討論