Functional Programming 與 Redux reducer

Functional programming(FP) 為近幾年較流行的開發方式,它與 OOP 一樣是屬於一種程式開發的觀念,而不是某種新起的語言或是套件。FP 的核心觀念就是程式是以 pure function 所組成,這裡會以 pure function 為起點並介紹其好處,最後以 redux reducer 為例介紹其應用。

Pure Function

介紹 pure function 時常以數學函式作為範例,以下為數學而非程式語言:

f(x) = 2x
f(2) = 4

相信這段式子對每個人而言並不陌生,而它卻是 pure function 的核心觀念之一,同樣的輸入會產生出同樣的結果。以程式舉個例子來說:

function multiple2(x) {
    return x * 2;
}

此一例中,這個 function 不管執行幾次,只要是帶同一個值,所得到的結果都不會改變,而舉另一個反例來說:

function getDate() {
    return new Date();
}

因為每次執行這個 function 所得到的值不會一樣,所以並不符合 pure function 的精神,另外像 Math.random() 之類的也都為一種反例。

再者, pure function 不會有副作用,意思是不會修改到外部已經宣告的值,舉例來說:

var foo = {
    bar: 'temp1'
}
function modify(temp) {
    temp.bar = 'temp2'
}
modify(foo); // { bar: 'temp2' }

這種直接修改外部參數的做法就會隱含副作用在裡面,因為若其他地方也使用了這個值,則在使用時可能因為這個值的修改而在其他地方得到不如預期的值。所以若要實作 pure function,則會建議以下寫法:

var foo = {
    bar: 'temp1'
}
function modify(temp) {
    return Object.assign({}, temp, { bar: 'temp2' });
}
var newFoo = modify(foo);
foo.bar; // temp1
newFoo.bar; // temp2

其實對於 pure function 也不用想得太過複雜,在 js Array 的 map, filter, reduce 就已經在做相同的事了。

好處?

  1. 因為不會有副作用,所以可以減少 bug 的發生。
  2. 當每個函式越純粹,就越能將函式拆分,只專心做好某部分的工作,所以更容易模組化。
  3. 當輸入與輸出不會變動時,當需要做複雜處理時可將處理過的值 cache 在 function 內,達到效能的優化。
  4. 在處理物件時,會產生新的物件而不影響原有物件,所以若有 bug 時易於追蹤是在哪個環節出錯,這部分會以 react reducer 為例在下段介紹。

React reducer

redux 的官方文件寫到:

Reducers are just pure functions that take the previous state and an action, and return the next state.

因為 pure function,每次 state 有變更時都會產生新的物件,所以在 redux 中很容易做到 time travel。我以 redux-devtools 中 todomvc 的 reducer 為例:

一個 todos 的 reducer 是一個 function,它會根據傳入 state 與 action type 來回傳一個新的物件。以下就 ADD_TODO、DELETE_TODO、EDIT_TODO 三項狀態來說明運作的流程。

ADD_TODO

這裡會回傳一個新的陣列,這個陣列將新加入的 todo 放在第一個,後面的 …state 是 es6 的寫法,在陣列中這樣寫會將原本陣列的物件複製一份,所以最後會回傳一個全新的陣列

DELETE_TODO

將要刪除的 id 比對後過濾掉,使用 Array 的 filter 返回一個新的陣列。

EDIT_TODO

比對要編輯的 id,並將給予新的 text,最後以 map 的方式傳回新的陣列。

綜觀以上,reducer 並不會修改原有的 state,而是都傳回新的物件,達到 pure function。這樣的做法在使用 redux-devtools 時,每一次 state 的變動都會記錄下來,大大降低了 js 在 debug 上的難度。

初始狀態,可以看到 todos 陣列中只有一筆資料。
加入一筆資料後 todos 的資料因為 ADD_TODO action 的變更變成兩筆。

參考:

Master the JavaScript Interview: What is Functional Programming?

Master the JavaScript Interview: What is a Pure Function?

mostly-adequate-guide-chinese

實現 JavaScript 的 Memoization

redux-devtools