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

独立开发者8个月前 (09-28)RxJS系列146

Functional Programming 是 Rx 最重要的观念之一,基本上只要学会 FP 要上手 Rx 就不难了!Functional Programming 可以说是近年来的显学,各种新的函式编程语言推出之外,其他旧有的语言也都在新版中加强对 FP 的支援!

什麼是 Functional Programming ?


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 等问题。


相关文章

30 天精通 RxJS (00):关于本系列文章

前言笔者从去年就一直想参加铁人赛 30 天,一方面是希望利用机会把自己的所学做一次整理,另一方面想训练自己组织文章的能力。去年的时候我想写 ECMAScript 2015,也准备了一段时间,结果没想到...

30 天精通 RxJS (01):认识 RxJS

RxJS 是笔者认為未来几年内会非常红的 Library,RxJS 提供了一套完整的非同步解决方案,让我们在面对各种非同步行為,不管是 Event, AJAX, 还是 Animation 等,我们都可...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。