數學之美
把數學的美妙絕倫傳遞給一個六歲的女孩兒並不是件容易的事情。我們可以輕而易舉地心算出任何一個 100 以內的數乘以 3 的結果,也可以道出「乘法是加法的累進」這樣的總結,但六歲的孩子並不理解其中的奧祕。前兩天我老婆許是受了數獨的啟發,在白紙上畫了十乘十的格子,給小寶演示 100 以內任意一個數乘三,乘七,乘十一的結果(因為除法是乘法的逆運算,你也可以理解為可以被三,七,十一整除的數字),當枯燥的數字化作了圖形,並且這圖形展現出統計意義的規律,小寶本來對數學的好感被大大激發了,從這些圖形中發現了很多有趣的事情。老婆便讓我做個「簡單」的應用,讓小寶能夠更好地瞭解數字及其背後的運算。
這是個巨集大的命題 —— 我只能以小寶剛剛可以理解的乘法除法開始。程式碼寫完之後隨便調了幾個數字一執行,我都不敢相信自己的眼睛 —— 原來那些簡單的運算,還有如此美妙的分佈:

注意綠色數字從右上到左下個位數字的 9,8,7 … 3,2,1 和十位數字的 1,2,3,4,5 …

左上到右下個位數字的 1,2,3,4,…,十位數字隔行 2,4,6 …,1,3,5 …

右上到左下也是個位數字9,8,7 … 3,2,1 和十位數字的 1,2,3,4,5 …,更齊整

左上到右下個位和十位都以一遞增,上百位之後個位數 1,2,3,4,5,…,十位數 2,3,4,5,… 齊整漂亮

左上到右下十位數以一遞增,逢三跳一,右上到左下,個位數又是 9,8,7,6,5,… 的規律

右上到左下個位數9,8,7,6,…,十位數重複兩次交替 1,3,5,7,9,和 0,2,4,6,8
這些規律是小寶和我在玩的過程中一點點發現的,大部分要歸功於她,我只是做一點小小的總結和啟發。
當然,本文的重點不在於這些對你而言過於簡單的四則運算。我們是程式設計師,數學給了我們穿越時空的眼睛,我們要用它探究程式的奧義。
冪等(idempotent)
冪等是很多程式設計師的裝逼利器。在 code review 大家激烈撕逼的當口,你淡定而優雅地吐出一個菸圈,緩緩掐滅手頭的菸蒂,清一清喉嚨,嘶啞地來一句:這個 API 冪等麼?絕對秒殺現場 99% 的格子衫。如果你還能夠正確地拼對並且讀對 idempotent,基本可以一戰封候,不愁找不到女朋友。
那麼究竟什麼是冪等?
Idempotent is the property of certain operations in mathematics and computer science whereby they can be applied multiple times without changing the result beyond the initial application. - wikipedia
說白話就是:冪等是指被自己重複運算,結果還等於自己。說人話就是:f x f = f。這個規則放在計算機世界裡,稍微弱化成一個函式 f(x) 可以被呼叫任意多次,其副作用保持不變。注意,這裡的不變並不意味著沒有副作用 —— 比如刪除資料庫裡的一條主鍵為 k 的記錄,無論呼叫多少次,其副作用都是 k 這條記錄不存在。從這個意義上講,沒有副作用的純函式(pure function)一定是冪等的,冪等函式不一定是純函式。
冪等的好處是帶來(副作用的)確定性 。HTTP GET / PUT / DELETE 被設計成冪等的,是因為資源的獲取,替換和刪除無論被呼叫一次還是多次,資源的狀態保持不變。這樣,呼叫者可以多次呼叫(重新整理頁面)而不必擔心引發錯誤。
在一個分散式的世界裡,冪等是皇冠上的鑽石。我們要儘可能(或者說不得不)將系統設計為冪等,來應對各種各樣的不確定性。比如郭靖通過網際網路給華箏轉賬 10 個金葉子,華箏可能沒收到,也可能收到了但是給靖哥哥的回覆在網路上丟了,兩種情況郭靖都需要重傳這筆交易,然而重傳導致的後果可能是郭靖明明只需要給華箏轉 10 個金葉子,卻轉了 20 個。如果設計成為冪等,我們可以在轉賬交易中加入一個唯一標識,這樣重複的轉賬就會被丟棄,從而保證一致的副作用。
我們把這個例子稍微抽象一下。在一個訊息處理的系統裡,如果我們能有以冪等的方式處理訊息 —— 就是說同一個訊息,我收到 (1, n) 次,其副作用是不變的 —— 那麼很多複雜的事務性的問題就迎刃而解了,同時我們也可以降低訊息系統的複雜性 —— 我們知道,在一個訊息系統裡,訊息送達的模式有: at least once 或者 at most once 。這兩種都很好實現,但要想能夠達到 exactly once 則幾乎是 mission impossible。
這中訊息處理的冪等能力在網路協議中很多見,主要用作 anti replay;以太坊的世界裡,transaction nonce 就是這個作用。
交換律(commutative)
和冪等不同的是,交換律及下面要談到的結合律都是小學數學就會涉及的範疇(雖然 f ( x ) = x * 1, g ( x ) = x /1, h ( x ) = x + 0 這些顯然也是冪等的範疇,但小學不會引入冪等的概念),不到特殊場合,拿出來裝逼只能貽笑大方。
交換律是說給定任意 x, y,對於運算 “*“,如果 x * y = y * x ,那麼我們就認為這個運算是可交換的。拿人話來說就是我們可以改變處理的事件的順序而不影響其最終的結果。對我女兒來說,她很容易理解(加法的)交換律:

在計算機的世界裡, 交換律意味著我們可以打算指令(或者訊息)的順序,進行亂序執行 。在我們這個熱力學第二定律統治的宇宙下,亂序執行一定比順序執行更有能效。在一個訊息系統裡,如果訊息要按照發送的序列嚴格處理,就意味著在接收端需要使用佇列來儲存和排序已經收到的訊息,前一個訊息沒有處理,不能處理下一個訊息,那麼,這樣的系統效率比較低;如果我們能夠將其改進成為訊息可以按照收到的順序處理,也就是滿足了交換律,那麼,效率可以成量級地提高。
假設我們要計算從網路另一端傳輸過來的一到一百的數字之和,傳輸過來順序可能是:
2 7 3 8 1 9 6 5 4 …
如果我們的演算法不滿足交換律,那麼它需要花 O( nlogn ) 到 O( n 2) 的時間維護有序列表,同時需要 O(n) 的空間來維護等待處理的佇列(比如:收到 1 之前的,排好序的 2 3 7 8)。系統最壞的延遲是 na + nb + n 2 c (假設 a 是傳輸一個元素的延時,b 是一次加法所需要的時間,c 是在列表中移動一次並比較的時間)。
如果滿足交換律,那麼就意味著我收到 2,就可以計算 0 + 2 = 2,收到 7,計算 2 + 7 = 9,一路下來,不需要花時間和空間維護列表,同時系統的延時只有 na + b (假設 a > b,在等待收下一個元素的時間 a 裡,當前的元素累加所花的時間 b 已經被包含進去)。
從上面的討論可以看到,在一個訊息系統裡,如果一個演算法能夠滿足交換律,那麼,其大大降低了系統的複雜性,也大大縮減了系統的延遲。
結合律(associative)
結合律是說給定任意的 x, y, z,對於運算 “*“,如果 ( x * y ) * z = x * ( y * z ),那麼我們就說這個運算是可結合的。拿人話來說就是,只要參與計算的元素的順序不變,我可以隨意改變運算的順序,而不會影響最終的結果。在初等數學裡,實數的加法和乘法都是可結合的,比如 (1 + 2) + 3 = 1 + (2 + 3)。
注意,滿足交換律並不意味著滿足結合律,比如運算 “*" 的定義是 f(x, y) = x 2 + y 2, f ( f ( x , y ), z ) = f ( z , f ( y , x )),即 ( x 2 + y 2)2 + z 2 = z 2 + ( y 2 + x 2)2,顯然滿足交換律,但:
f ( f ( x , y ), z )! = f ( f ( x ), f ( y , z )),即 ( x 2 + y 2)2 + z 2! = x 2 + ( y 2 + z 2)2。顯然不滿足結合律。
在計算機的世界裡, 結合律意味著計算的順序可以發生變化 。比如快排演算法,在 partition 之後,先處理小於基準點的陣列,還是先處理大於基準點的陣列,並沒有關係,所以它是滿足結合律的。
我們進一步思考,可以發現,滿足結合律意味著處理過程中的區域性狀態並不受全域性狀態的影響,或者說獨立於全域性狀態,於是, 結合律還意味著併發處理 —— 對於一個給定的列表,如果處理列表的演算法是滿足結合律的,那麼就意味著我們可以對列表中的子列表進行併發計算,而並不影響最終的結果。反過來表述,一切可以分治的演算法,都滿足結合律。
冪等 x 交換律 x 結合律:??
在一個分散式網路裡,訊息的傳遞是不確定的,這種不確定性有二:
- 訊息可能丟失。丟失是件很討厭的事情,於是我們加入重傳,這樣引入新的不確定性:訊息有可能重複。接收方可能收到 (1, n) 次。
- 訊息可能亂序。亂系意味著同一個網路中的 n 個節點,他們最終收到的訊息序列的順序可能互不相等,並且不可預測。
假設兩個節點 (A, B) 的網路,收到三個訊息 (x, y, z),它們看到的結果可能是:
- A: xzzyzy
- B: yyzzxxxzy
如果 A 和 B 肯定能收全所有訊息,並且根據這些訊息,可以推匯出一致的狀態 S,那麼我們就稱這個系統具備一致性。
一般而言,我們可以引入某種同步機制讓所有人看到順序一致的訊息 —— 比如資料庫系統會使用 leader election 選出主節點,由主節點來確定順序;而區塊鏈中會使用算力或者投票權通過某種共識演算法選擇出某一時刻的主節點,所有節點接受主節點確定的訊息順序。
然而,我們也可以在資料結構和對資料結構的處理上,同時引入冪等,交換律和結合律。這樣,從數學的角度,我們可以計算出 A 和 B 的最終狀態都會收斂在 x * y * z 上。
我們先不看怎麼設計資料結構和演算法,如果我們達到這個效果,那麼就意味著網路中的所有節點可以各自獨立地推匯出一致的狀態,不需要任何的同步,那麼,網路本身執行的效率將會非常高效,且能夠不付出太多代價的前提下滿足 Crash Fault Tolerance (CFT),甚至,經過一些改進,滿足 Byzantine Fault Tolerance(BFT)。
如果你覺得有意思,以後我們可以講講實現這個理想的資料結構 CRDT(Conflict-free Replicated Data Types)。
歡迎回到數學的世界!