今天要介紹 Functional Programming ( 簡稱FP ) ,FP 是一種程式設計的思考方式,寫程式寫過一段時間的人幾乎都會聽過這個概念,對某些人來說,想要進入資深階段的話,學習 Functional Programming 是一件不可或缺的事情。
Outline
- Functional Programming
- 為什麼要使用 FP
- 純函式 ( Pure functions )
- 複合函式( Function composition )
- 共享的狀態 ( Shared State )
- 不變性( Immutability )
- 避免副作用( Side Effect )
Functional Programming
Functional programming ( 簡稱FP ) 用比較嚴謹的說法,是一種程式設計方法 ( Programming Paradigm ),意味著他是一種根據某些基本原則來進行開發的軟體架構,聽名字應該可以了解他是以函式為主的開發方式,與之相對的是物件導向程式設計,指的是以 物件(Class)為主的軟體架構。
為什麼要使用 Functional Programming
使用 FP 可以讓程式碼看起來更簡潔,且對功能的描述更精準、所以也就更好進行測試,對開發來說有不少好處,但是如果你對 FP 以及相關的概念還沒有很熟悉,FP 的程式碼也可能讓你需要更多時間來閱讀。
Functional Programming 對初學者來說聽起來可能會有點嚇人,不過如果你是一個有一點經驗的開發者,那麼你可能其實已經使用過 FP 的概念了,只是你不知道而已。在你能夠真正了解什麼是 Functional Programming 之前,有幾個相關的概念必須先理解:
- 純函式 ( Pure functions )
- 複合函式( Function composition )
- 共享的狀態 ( Shared State )
- 可修改性 ( Immutability )
- 避免副作用( Side Effect )
也就是說,如果想要知道 FP 具體來說是什麼的話,就必須了解上述幾個基本概念,在今天的介紹裡面,我會把這些概念依序做簡單的介紹,下面就讓我們一個一個來看看吧。
Pure Function
純函式有很多對 Functional Programming 非常重要的特性,後面有許多進階概念都是基於純函式的概念演變出來的,純函式的特性包含:
- 同一個輸入純函式的參數,永遠都會回傳相同的結果。
- 純函式永遠都不會有造成 Side Effect 的操作出現,如 API 拉取、裝置的I/O、或者對函式外部資料的修改。
Functional Composition
複合函式的概念來自數學,是指如何組合兩個以上的函式並依照組合的順序去產生另外一個新的函式,或是做些運算。在數學裡面,我們常常用 f(g(x))
來表示複合函式,意思就是把 g(x) 運算產生的結果值,傳入 f()
函式裡面 。 JS 之所以也能做到類似的行為,是基於 JS 被稱為「一級函式」的概念。(把函式當作參數傳入另外一個函式)
舉例來說,我們想要表現 1 + 2 * 3
的話,可以用兩個函式來表示並組合:
const add = (a, b) => a + b;
const mult = (a, b) => a * b;
add(1, mult(2, 3))
我們寫了加法跟乘法的函式,並將兩個函式組合,就能夠表現出「先乘後加」的行為,這就是複合函式的基本概念。
Shared State
共享的狀態 ( Shared State )是指任何存在被數個分離的範疇。像是像全域範疇或是前面提到的閉包 ( Closure ) 所共享的這類狀態, 通常就是共享的狀態,在 Funtional Progaramming 裡面,共享的狀態應該避免,因為一但函式內有與其他範疇共享的狀態出現,那麼這個函式就不再是純函式了。 一個共享狀態的例子看起來就像這樣:
let age = 15
function setUserAgeByInfo( info ) {
age = info.age
return age
}
setUserAgeByInfo({age:100})
根據上面的程式碼,一但我們執行上面的函式之後,全域變數 userInfo 就會受到影響,這就是因為該變數(狀態)同時與全域範疇跟函式範疇共享的結果。這還只是比較小規模的例子,想想看,如果同時有十個函式都這樣使用全域變數,那麼會出現開發者無法避免的情況,也就不奇怪了,所以在使用狀態時,越是全域的狀態,就要越小心使用。
Immutability
當我們說一個物件是 Immutable ,那就表示這個物件在被產生之後,就無法再被修改了;反過來說,一個 Mutable 的物件,就是指在物件被產生後,還可以被修改,在 JS 內,用一般的方式產生的物件,就是這類 Mutable 的物件。不變性是 Functional Programming 的核心概念,因為如果沒有不變性的存在,我們在寫 FP 時就難以追蹤到狀態的歷史變化,奇怪的、無法理解的 Bug 就越有可能出現。
在 JS 的 ES6 版本後出現了使用 const
的宣告方式,const
很容易被跟不變性產生聯想,但其實是兩個不同的概念,const
是產生一個無法再被重新指派的變數而已,但是他並非產生一個 Immutable 的物件,不相信的話你可以試試看下面的程式碼:
const user = { name:'Yoda' }
user.name = 'Luke'
真正的 Immutable Object 可以用 Object.freeze 這個函式被產生出來:
const a = Object.freeze({
foo: 'Hello',
bar: 'world',
baz: '!'
});
a.foo = 'Goodbye';
// Error: Cannot assign to read only property 'foo' of object Object
在 JS 裡面,也有一些函式庫可以用來幫助你以完全 Immutable 的方式來開發,例如 Immutable.js 。
Side Effects
副作用(Side Effects)是指在被呼叫的函式外部,任何可以被看到的狀態改變,剛剛我們提到的狀態共享,就是有可能造成 Side Effect 的原因,Side Effect 的幾個例子如下:
- 使用consoie.log 印出值
- 寫入檔案
- 拉取第三方 API
- 呼叫其他任何有副作用的函式
副作用在 FP 內必須極力避免,因為如此一來,才能讓函式變得更簡潔,而且更好測試。
我們看了這麼多概念,其實有幾個概念幾乎是重複的,例如避免副作用、減少狀態共享、使用純函式,在我看來,這些概念都著重於「減少依賴」這件事情,也就是兩個不同部分的程式碼,他們所使用到的資訊應該要是完全獨立的,如此一來,也才能夠讓程式碼更乾淨好閱讀。
參考文章
Master the JavaScript Interview: What is Functional Programming?