戳破函數語言程式設計的泡沫
本文是 狀態管理框架 選型預研時看到油管上一個Talk的部分內容的總結和一些自己的思考。
要引入一個框架,從上到下的過程包括
狀態管理框架 可追根溯源到函式式的本質特性上,所以有必要先搞清楚函式式為何物。
而一個“新”事物的流行往往會產生泡沫,所以總 是會把函式式當成 銀彈 ,如同對待 區塊鏈 , 人工智慧 和 大資料 一樣。在這裡希望一方面把函式式的來龍去脈說清,另一方面戳破這些浮誇的 泡沫效應 ,讓它迴歸到原有的價值。
函式式從何而來
函式式的歷史可以追述到1930年代的{lambda calculus},函數語言程式設計本質上就是對 λ演算 的一種實現。基本思想就是 只通過“變數”和“函式”兩個元素來解決問題 。這個思想給函數語言程式設計帶來諸如 匿名函式 、 一等公民 、 柯里化 等概念。
我們所接觸到的主流程式語言幾乎都是 圖靈機 的思想延續,可見這一分支一直是處於絕對統治地位。但隨著現代語言越來越多的加入 λ演算 的特性,這兩個思想最終產生了交集。
那麼函式式思想為什麼從一個小眾的事物慢慢變成了顯學了呢?那麼我們看一下這個思想到底給軟體工程注入了哪些新鮮血液。
圖靈系語言的問題
有一種誤解是學習函式式 要忘記掉你所學過的程式設計方式 ,但與其說引入函式式就是要用它 替代 面向物件 ,還不如說是對 面向物件 的程式進行 重構 ,畢竟兩者各有優劣。
我們一開始滿足某個業務寫的 面向物件 程式碼可能是以下的樣子,你可以把他看做一些頁面,函式或是模組。

隨時業務的變化,可能有些元件被刪掉,或是新增了新的模組,也可能是模組間的呼叫/依賴關係有了變化。於是複雜度開始增大。

程式碼變成這個樣子後,雖然表面上仍然可以使用,但是原始碼卻已經 難以理解和維護 我們會發現難以理解的一個原因是: 這個系統一件事的成立,需要大量的前提! ++因為依賴太多++
圖靈系的語言也有一樣的困境,隨著語言的迭代更新,逐漸加入了更多的限制:
- 比如 Ruby 用{protected method}表示一種函式讓其只能被自己和子類所訪問
- C++ 引入{friend function}來表示一個函式不是類的成員函式但是能訪問類的私有成員
當一個概念越來越 難以理解 的時候,就到了一個改變的節點上。比如減少概念:Swift就沒有protect關鍵字 [why];但更有效的思路是引入更加 簡單 的 函式式 。
函式式的函式
數學家,和程式設計師一樣,對概念的_封裝_,_組合_有著巨大的需求。 例如英國數學家在一本著作中花了 379頁 推匯出 1+1=2



他們是怎麼管理好這麼多封裝,最終能串聯起來解決問題的呢? 可否把他們的經驗拿來借鑑?
答案就是 數學函式

數學函式 是 輸入集合到輸出集合對映 。

而函式式語言的函式特性就是 數學函式
這和 面向物件 的函式可不一樣,我們一般而言的 函式/方法 ,都是一些statement的集合。也就是說,他可以對外界環境進行改造 (比如網路請求,資料庫請求),也可以完全沒有輸入和輸出,更不用提說什麼映射了。

重構1:加入純函式
但其實要想借鑑過來也很簡單,比如大多現代語言都引入了 閉包 , 一等公民 ,在此基礎上只需要給日常所寫的函式加一個 約束 ,就可以做到類似數學函式的效果。(加了這些約束的函式被稱為 純函式 ,或 引用透明 )這個約束就是: 沒有副作用 (只能加工引數,輸出結果,不能修改外界環境)
如果我們平時多寫純函式,問題就已經得到了一定的簡化。
重構2:加入不可變特性 Immutable
純函式 帶來的一個好處就是 不可變 immutability ,因為 純函式 只能處理輸入,返回輸出,那外界的狀態就可以通過 拷貝 的方式傳遞進來,不用修改原來的值:
比如對於把x這個集合傳入各種各樣的函式內,x並不會改變。

這個好處不言而喻,不可變意味著執行緒安全,意味著不必因為一些bug去檢視每一個可能產生的函式。
例如Swift就引入了這個特性,除了 class 都是 值型別 ,傳遞即拷貝,並通過 copy-on-write 實現按需拷貝,降低空間開銷。
重構3:控制副作用
純函式 不是一個褒義詞,如同 副作用 不是一個貶義詞一樣。
我們程式設計師獲得薪水的原因就是 編寫程式通過寫一些不純函式來產生副作用 (例如改變資料庫裡的資料)
使用者不關心程式碼,只關心你能帶給他們什麼樣的 副作用 ,只有程式設計師才關心這個過程夠不夠優雅。
問題在於如何處理 副作用 ?
答案是 橋 ,換句話說就是一層封裝,把真正要產生的 副作用 藏起來。呼叫 橋 只能通過一個 如何修改該值 的函式給它,由這層封裝來幫你實現 副作用 。
如果是狀態修改,有一種方式是:當有衝突發生的時候,會重新執行需要再次更新的函式。(類似資料庫的transaction)

如果是修改外部環境,一般會把請求放到佇列裡,統一派發。

即使是函式式語言, 副作用 也是要做的,不然這個程式就毫無意義,只是我們把 副作用 和 純函式 隔離了起來,讓他們的影響在一個可控的範圍內。
這種隱藏狀態,通過傳遞函式修改狀態的管理 副作用 的方式,就是我們下面要聊的狀態管理思想。
總結,函式式帶來的最大好處?
那就是併發!
共享全域性狀態是維護的噩夢,單純的亂程式碼還不足以造成無法理解,可以通過清理最終理清,但如果是併發問題的程式碼,需要釜底抽薪的改造才可能把各種執行緒間的協作交接清楚。
即使我們不引入 狀態管理框架 ,僅僅是讓陣列不可變,用Promise/RxSwift之類把非同步的程式碼梳理好,也 至少 能讓出現問題的執行緒拿到了不同版本的資料,由於資料間的隔離就不會有這邊遍歷那邊修改導致的閃退。
如果在應用了Promise/RxSwift之外,引入了 狀態管理框架 ,就意味著有了一個方便的途徑,可以直接宣告一種安全的共享狀態,所有的修改都是統一控制的,不僅僅不會出錯,還可以讓每次修改都能有序的生效。