iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 3
8
Modern Web

30 天精通 RxJS系列 第 3

30 天精通 RxJS (02): Functional Programming 基本觀念

30 天精通 RxJS (02): Functional Programming 基本觀念

Functional Programming 是 Rx 最重要的觀念之一,基本上只要學會 FP 要上手 Rx 就不難了!Functional Programming 可以說是近年來的顯學,各種新的函式編程語言推出之外,其他舊有的語言也都在新版中加強對 FP 的支援!

這是【30天精通 RxJS】的 02 篇,如果還沒看過 01 篇可以往這邊走:
30 天精通 RxJS (01): 認識 RxJS

什麼是 Functional Programming ?

functional programming icon

Functional Programming 是一種編程範式(programming paradigm),就像 Object-oriented Programming(OOP)一樣,就是一種寫程式的方法論,這些方法論告訴我們如何思考及解決問題。

簡單說 Functional Programming 核心思想就是做運算處理,並用 function 來思考問題,例如像以下的算數運算式:

(5 + 6) - 1 * 3

我們可以寫成

const add = (a, b) => a + b
const mul = (a, b) => a * b
const sub = (a, b) => a - b

sub(add(5, 6), mul(1, 3))

我們把每個運算包成一個個不同的 function,並用這些 function 組合出我們要的結果,這就是最簡單的 Functional Programming。

Functional Programming 基本要件

跟 OOP 一樣不是所有的語言都支持 FP,要能夠支持 FP 的語言至少需要符合函式為一等公民的特性。

函式為一等公民 (First Class)

一等公民就是指跟其他資料型別具有同等地位,也就是說函式能夠被賦值給變數,函式也能夠被當作參數傳入另一個函式,也可當作一個函式的回傳值

函式能夠被賦值給變數

var hello = function() {}

函式能被當作參數傳入

fetch('www.google.com')
.then(function(response) {}) // 匿名 function 被傳入 then()

函式能被當作回傳值

var a = function(a) {
	return function(b) {
	  return a + b;
	}; 
	// 可以回傳一個 function
}

Functional Programming 重要特性

Expression, no Statement

Functional Programming 都是 表達式 (Expression) 不會是 陳述式(Statement)。
基本區分表達式與陳述式:

表達式 是一個運算過程,一定會有返回值,例如執行一個 function

add(1,2)
  • 陳述式 則是表現某個行為,例如一個 賦值給一個變數
a = 1;

有時候表達式也可能同時是合法的陳述式,這裡只講基本的判斷方法。如果想更深入了解其中的差異,可以看這篇文章 Expressions versus statements in JavaScript

由於 Functional Programming 最早就是為了做運算處理不管 I/O,而 Statement 通常都屬於對系統 I/O 的操作,所以 FP 很自然的不會是 Statement。

當然在實務中不可能完全沒有 I/O 的操作,Functional Programming 只要求對 I/O 操作限制到最小,不要有不必要的 I/O 行為,盡量保持運算過程的單純。

Pure Function

Pure function 是指 一個 function 給予相同的參數,永遠會回傳相同的返回值,並且沒有任何顯著的副作用(Side Effect)

舉個例子:

var arr = [1, 2, 3, 4, 5];

arr.slice(0, 3); // [1, 2, 3]

arr.slice(0, 3); // [1, 2, 3]

arr.slice(0, 3); // [1, 2, 3]

這裡可以看到 slice 不管執行幾次,返回值都是相同的,並且除了返回一個值(value)之外並沒有做任何事,所以 slice 就是一個 pure function。

var arr = [1, 2, 3, 4, 5];

arr.splice(0, 3); // [1, 2, 3]

arr.splice(0, 3); // [4, 5]

arr.slice(0, 3); // []

這裡我們換成用 splice,因為 splice 每執行一次就會影響 arr 的值,導致每次結果都不同,這就很明顯不是一個 pure function。

Side Effect

Side Effect 是指一個 function 做了跟本身運算返回值沒有關係的事,比如說修改某個全域變數,或是修改傳入參數的值,甚至是執行 console.log 都算是 Side Effect。

Functional Programming 強調沒有 Side Effect,也就是 function 要保持純粹,只做運算並返回一個值,沒有其他額外的行為。

這裡列舉幾個前端常見的 Side Effect,但不是全部

  • 發送 http request
  • 在畫面印出值或是 log
  • 獲得使用者 input
  • Query DOM 物件

Referential transparency

前面提到的 pure function 不管外部環境如何,只要參數相同,函式執行的返回結果必定相同。這種不依賴任何外部狀態,只依賴於傳入的參數的特性也稱為 引用透明(Referential transparency)

利用參數保存狀態

由於最近很紅的 Redux 使我能很好的舉例,讓大家了解什麼是用參數保存狀態。了解 Redux 的開發者應該會知 Redux 的狀態是由各個 reducer 所組成的,而每個 reducer 的狀態就是保存在參數中!

function countReducer(state = 0, action) {
// ...
}

如果你跟 Redux 不熟可以看下面遞回的例子

function findIndex(arr, predicate, start = 0) {
    if (0 <= start && start < arr.length) {
        if (predicate(arr[start])) {
            return start;
        }
        return findIndex(arr, predicate, start+1);
    }
}
findIndex(['a', 'b'], x => x === 'b'); // 找陣列中 'b' 的 index

這裡我們寫了一個 findIndex 用來找陣列中的元素位置,我們在 findIndex 中故意多塞了一個參數用來保存當前找到第幾個 index 的狀態,這就是利用參數保存狀態!

這邊用到了遞回,遞回會不斷的呼叫自己,製造多層 stack frame,會導致運算速度較慢,而這通常需要靠編譯器做優化!

那 JS 有沒有做遞回優化呢? 恭喜大家,ES6 提供了 尾呼優化(tail call optimization),讓我們有一些手法可以讓遞回更有效率!

Functional Programming 優勢

可讀性高

當我們透過一系列的函式封裝資料的操作過程,程式碼能變得非常的簡潔且可讀性極高,例如下面的例子

[9, 4].concat([8, 7]) // 合併陣列
      .sort()  // 排序
      .filter(x => x > 5) // 過濾出大於 5 的

可維護性高

因為 Pure function 等特性,執行結果不依賴外部狀態,且不會對外部環境有任何操作,使 Functional Programming 能更好的除錯及撰寫單元測試。

易於併行/平行處理

Functional Programming 易於做併行/平行(Concurrency/Parallel)處理,因為我們基本上只做運算不碰 I/O,再加上沒有 Side Effect 的特性,所以較不用擔心 deadlock 等問題。

今日小結

今天講了 Functional Programming 的基本特性,及其優勢。現在愈來愈多的 Library 用到了 FP 的觀念,JS 也越來越多 Functional 的函式庫,例如:Lodash, Underscore, lazy, Ramda。了解 FP 的基本觀念有助於我們在學習其他 Library 更容易上手,也能使我們撰寫出更好的程式碼,希望各位讀者有所收穫,若有任何疑問歡迎在下方留言給我!


上一篇
30 天精通 RxJS (01):認識 RxJS
下一篇
30 天精通 RxJS (03): Functional Programming 通用函式
系列文
30 天精通 RxJS30

2 則留言

2
weiclin
iT邦高手 6 級 ‧ 2016-12-19 01:46:09

已訂閱, 請務必堅持下去 xD

看更多先前的回應...收起先前的回應...
JerryHong iT邦新手 5 級 ‧ 2016-12-19 11:09:54 檢舉

恩恩 我會想辦法撐下去的!!

撐不下去就留下你的電話號碼、line、skype……讓大家每天在23:30分時提醒你……你還有半個小時可以補上!

iamya iT邦新手 2 級 ‧ 2016-12-19 15:46:42 檢舉

/images/emoticon/emoticon01.gif

weiclin iT邦高手 6 級 ‧ 2016-12-19 15:55:19 檢舉

Sam 說的有理, 請交出電話號碼

JerryHong iT邦新手 5 級 ‧ 2016-12-19 16:18:25 檢舉

不行啦 這樣我光接電話就不用寫文章了XD

feng619 iT邦新手 5 級 ‧ 2016-12-19 18:35:23 檢舉

等待下一篇好煎熬啊~ ˊvˋ

JerryHong iT邦新手 5 級 ‧ 2016-12-20 02:25:34 檢舉

03 篇終於全部整理好了
http://ithelp.ithome.com.tw/articles/10186703

1
Mos
iT邦新手 5 級 ‧ 2017-01-29 22:05:49

FP 關於不用擔心 deadlock的部分我有點疑義
沒有 side effect 所以不會 deadlock
這樣的說法太模糊了

In computer science, functional programming is a programming paradigm—a style of building the structure and elements of computer programs—that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data.

這段話的重點在最後,因為 FP 基本就設計在 stateless 和 immutable data

也就是說
沒有 mutable data 沒有 state -> 不用擔心 synchronize -> 避免 deadlock

不知道我的認知有沒有錯誤,還請指正一下

JerryHong iT邦新手 5 級 ‧ 2017-01-29 23:58:08 檢舉

你說的沒錯,這裡是有意不提 immutable data 的。

之所以會這樣做是因為原生的 JS(不是真的 functional programming language) 沒有真正的 immutable data。(除非使用某些 library 輔助)

沒有 immutable data 會衍伸出一些很細的問題,而我不希望讓這些小細節困惑讀者。

JerryHong iT邦新手 5 級 ‧ 2017-01-30 00:02:53 檢舉

舉個例子,因為 JS 沒有 immutable data 的緣故,下面這個 function 無法判斷是否為 pure(沒有 side effect)

function sum(arr) {
  var result = 0;
  for (var i = 0; i < arr.length; i++) {
    result += arr[i];
  }
  return result;
}

我要留言

立即登入留言