跳至主要内容

[JS] Functional Programming and Currying

Understanding Currying in JavaScript

Functional Programming 是一種程式撰寫的風格,會把函式當成參數傳入,並以沒有 side effect 的方式回傳另一個函式。這也衍伸了一些概念:

  • Pure Function
  • Currying
  • Higher-Order Function

什麼是 Pure Function

Pure function 有兩個明確的定義:

  1. 當帶入相同的參數時,一定會得到相同的結果,又稱作 deterministic
  2. 它不會造成任何可觀察到的副作用(observable side effects),所有的變數多是 immutable 的。

當一個函式是 pure functionimmutable 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 Function 的實作

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 applicationCurrying 是相關的,但設計邏輯上並不一樣。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

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

Higher Order Function

當我們談到 higher-order functions 時,我們指的是:

  • 接受一個以上的函式當作參數
  • 最後會回傳出一個函式

參考