昨天的例子其實不需要特別轉成ImmutableList,因為linq的操作本身就具有不可變的概念,不可變得集合在多執行緒的時候具有很大的優勢,我們不需要考慮當共用資料時的競爭。不過在平常的操作中不會常常要用到多執行緒,今天來討論一下不可變性在單緒的情況下對寫程式有什麼幫助。
假設今天伊甸學園入學考試結束,入學名單會交由第三方檢查完畢後,再由校長宣布入學結果
// 取得入學名單
List<string> studentList = GetstudentList();
// 交由第三方套件檢查入學名單
ListChecker.Review(studentList);
List<string> nameList = new List<string> {
"達米安","貝琪","艾彌爾","安妮亞"
};
foreach(var name in nameList)
{
// 逐個宣布入學考試結果
if(studentList.Contains(name)) Console.WriteLine(&"{name} 可以入學");
}
// 第三方套件
class ListChecker(){
// 第三方套件被黃昏賄絡了 偷偷把安妮雅加到名單裡面
static void Review(List<string> s){
s.Add("安妮亞");
}
}
原本為了要確認入學名單正確,所以交給第三方套件檢查名單有沒有問題,殊不知第三方套件早就被賄賂了,偷偷把安妮亞加到名單裡面,於是成功混入伊甸學園可以偷到情報,可喜可賀!如果換成了不可變的集合,第三方套件就沒辦法神不知鬼不覺的偷改名單。
昨天結尾提到了要檢討前幾天用的Stack:
"+" => PushIntoStack(s, (s.Pop() + s.Pop())),
這一小段希望將stack中最上層的第一項與第二項拿出來相加,然後將結果放回堆疊,不知道你是不是跟我一樣過了幾天回來看這段程式碼的時候有一點困惑?這一小段利用到了可變堆疊,寫的當下我知道會依序執行
可是我怎麼知道先拿出來的是A還是B,加法計算還好,如果是減跟除,計算的結果就天差地遠了。此外,要這麼剛好的知道兩數的和會被放到剛剛拿出兩個元素的堆疊,就需要對這個資料結構與方法十分熟悉。如果是小型的程式,而且只有自己在寫那也還好,可是當程式碼行數一多,而且需要多個人協作,無形中就增加了理解的成本和改錯的風險。
如果將stack改成ImmutableStack,那我應該會改成
"+" => s.Pop(out var a).Pop(out var b).Push(a+b)
覺得這樣寫是不是漂亮許多呢?整個計算的順序清楚的表示出來,Pop → Pop → Push這個流程又可以進一部封裝起來給其他的計算使用。
對FP來說不可變是極為重要的觀念,觀察上面ImmutableStack,原來mutableStack的Pop方法藉由副作用改變堆疊的狀態達到目的,而對於不可變的Stack,Pop就具有函數映射的特性,並沒有改變狀態,也讓程式碼容易預期。
ImmutableStack踩到的小雷,ImmutableStack不可以直接建構,需要用靜態Create方法取得
在C#中有幾類的集合,分別是
這邊特別補充一下,在多執行緒的時候可以使用Immutable或是Concurrent集合,微軟針對不同的情況有建議,簡要如下