在前幾天的內容中常常提到純函數、副作用,但究竟是什麼意思呢?我覺得並不好解釋,但是我們可以觀察:
給定相同的輸入,就會得到相同的輸出
在Day2與Day3,我們講到了函數的對應關係,而我們對希望純函數不會因為外界的狀態改變而改變他的對應關係,舉例來說:
Math.Round()
是純函數,給定Round(5.1,0)
就會得到5,不會因為轉生到異世界就變成6(不過異世界通常好像都沒有電腦)。string.ToUpper()
是純函數,輸出的文字不會有小寫英文字母。HttpClinet
的 GetAsync
不是純函數,回傳值會取決於當下的網路環境,有可能是200也有可能是404。DateTime.Now()
不是純函數,因為得到的值總是不一樣純函數不會改變狀態
不改變狀態這一點,其實就是所謂的副作用,也是不變性在FP為什麼重要:
List<T>.Sort
不是純函數,因為他直接改變了原來的集合Enumerable.OrderBy
是純函數,他不改變原來的集合而是返回新的值Console.WriteLine
不是純函數,他改變了螢幕的狀態純函數的優點來自於限制,因為我們限制了輸入與輸出以及不能有副作用,所以在設計階段就獲得了
純函數容易理解
函數只關注輸入與輸出,我們不需要理會函數裡面的行為
var number = "一";
// 對於能不能轉換成功沒有定義
var n1 = int.Parse(number);
// 可以進一步包裝成Option,因為結果能夠預期,更容易處理
var isInt = int.TryParse(out var n2);
純函數容易測試
舉例來說,當程式碼裡面用到了DateTime.Now()就會不容易測試,因為當下的時間會不斷的改變。
public bool CheckIfExpired(Datetime datetime) => datetime > Datetime.Now();
在OOP的做法中,我們可能會用一個物件去包裝Datetime.Now(),並且在撰寫測試時Mock這個物件,使取得當下時間是固定的。
但是在FP中,我們希望寫純函數,所以設計上會變成
public bool CheckIfExpired(TimeSpan span) => span > 0
今天大概講了一下純函數的概念,主要是為了明天的題目做鋪陳,FP中透過模組包裝函數,實現資料與行為分離,而明天要討論C#中如何實現模組的概念。