[JS] Functional Programming and Currying
Functional Programming 是一種程式撰寫的風格,會把函式當成參數傳入,並以沒有 side effect 的方式回傳另一個函式。這也衍伸了一些概念:
- Pure Function
- Currying
- Higher-Order Function
什麼是 Pure Function
Pure function 有兩個明確的定義:
- 當帶入相同的參數時,一定會得到相同的結果,又稱作 deterministic。
- 它不會造成任何可觀察到的副作用(observable side effects),所有的變數多是 immutable 的。
當一個函式是 pure function 且 immutable data,那麼我們可以稱作 referential transparency
immutability 指的是不會隨著時間改變。
什麼是 Currying
Currying 是 functional programming 中的一種過程,我們會將函式當成參數帶入另一個函式的參數中,因此變成嵌套的函式(nesting functions),而這個函式會回傳一個新的函式。
在 Currying 中,會把帶有多個參數(arity)的函式,轉換成一次帶一個參數的多個連續函式。
// 2-arity function
function fn(a, b) {
//...
}
// 3-arity function
function _fn(a, b, c) {
//...
arity:指的是一個函式有的參數數目,近似 arguments
將一般的函式轉成 Currying function:
// 原本的函式帶有三個 arity
function multiply(a, b, c) {
return a * b * c;
}
multiply(1, 2, 3); // 6
轉成 currying function 後:
const multiply = (a) => (b) => (c) => {
return a * b * c;
};
multiply(1)(2)(3); // 6
// 等同於下面的寫法
function multiply(a) {
return function (b) {
return function (c) {
return a * b * c;
};
};
}
Curry @ Leetcode
const curry = (fn) => {
return curriedFn(...args) {
// fn.length 可以取得 fn 可以帶入的參數數量
// args.length 表示在執行這個 fn 時,帶入的參數數量
if (fn.length < args.length) {
return fn(...args);
}
return (...nextArgs) => curriedFn(...args, ...nextArgs);
}
}
什麼是 Partial Function Application
Partial application 和 Currying 是相關的,但設計邏輯上並不一樣。Partial application 著重在先將某個函式的部分參數值固定,然後生成一個新的函式。這個新的函式只需要接收剩下的參數即可。例如:
// Partial Function Application
function volume = (h, w, l) => {
return h * w * l;
}
const partialVolume = (w, l) => {
// 把 height 先固定起來
return volume(2, w, l);
};
console.log(partialVolume(90, 30));
兩者一樣的地方在於,它們都依賴閉包(closure)才能作用。
Partial Application vs Currying @ Leetcode
Currying / Partial Application 的好處
為某 一個函式設定設置
例如,我們有一個計算折扣的函式,在還沒使用 currying 之前,可以寫成這樣:
function getDiscountPrice(price, discount) {
return price * discount;
}
let price = getDiscountPrice(100, 0.9);
這個的麻煩之處在於,即時全館 9 折,但我們在使用這個函式時每次都還是要把 discount 代到參數中。如果我們使用 currying 則可以制定一個專門就是 9 折用的函式:
const getDiscountPrice = (discount) => (price) => {
return price * discount;
};
// 都是九折的情況
const get10PercentOff = getDiscountPrice(0.9);
let price = get10PercentOff(100); // 90