函數式編程介紹
本文將描述函數式編程的基本理論,從而讓你理解什麽是函數式編程,同時也會展示“函數式編程”和“面向對象”這兩種不同風格的編程範式之間的區別。本文是函數式編程系列的入門篇,後續文章陸續會發出。
什麽是函數
函數式編程背後的理論依據是數學,數學函數背後有一系列有意思的特點,而函數式編程語言則試圖模擬這些特性。
讓我們先來看一個簡單的數學函數:
y = x + 1
這個數學函數的意圖是顯而易見的,給定一個變量x然後返回x + 1, 上面的數學函數用C#來表示:
public int Add1(int x)
{
return x + 1;
}
數學函數跟我們用命令式語言編寫的函數相比有兩個明顯的區別:
- 同一個輸入在數學函數裏總能得到相同的返回值
- 數學函數沒有副作用
- 輸入值和輸出值是不變的(
immutable
)
這三個區別具體是什麽意思呢?
同一個輸入在數學函數裏總能得到相同的返回值
在命令式語言(imperative programming)中,我們總是在函數內部做一些計算和處理,然後返回最終結果。在數學函數中沒有計算和處理,你可以認為數學函數的返回值是對輸入值的一一映射。上面提到的數學函數極端定義如下:
int Add1(int x) { switch (x) { case 0: return 1; case 1: return 2; case 2: return 3; case 3: return 4; //... } }
當然現實環境中我們不會寫出這樣的代碼,但是數學函數的思想是類似的,裏面沒有對事情的處理邏輯。
關於命令式語言和聲明式語言的區別看這裏:命令式語言和聲明式語言的區別
數學函數沒有副作用
數學函數所做的事就是根據每一個輸入返回不同的輸出,整個過程沒有對輸入值做任何改變,在返回輸出的過程中也沒有對其他的任何環境造成影響,在函數式語言裏這樣的函數被稱為純函數
。
輸入值和輸出值是不變的
immutable使得代碼更加具有預測性,考慮下面的代碼:
public int Add1(int x) { x = 2; return x + 1; } public void InvokeAdd1() { var x = 10; var y = Add1(x); Console.WriteLine($"x={x}, y={y}"); }
你試圖在Add1
函數中實現y = x + 1
,但是由於失誤將x的值改為了2,從而導致了一個bug,immutable則強制你無法修改x的值,一旦初始化就不能再修改。顯然C#默認是mutable的,但在常見的函數式語言中,變量聲明式immutable的,比如在F#中將x初始化為6然後再修改為7會編譯出錯:
let x = 6
x <- 7
作為一個修煉命令式語言多年的開發者也許會內心充滿疑問,無法修改變量真的靠譜?答案是肯定的,在後面的文章中將會展示這種用法。
純函數帶來的好處
看似不起眼的三個特點實際上會帶來非常強大的益處,所以函數式編程語言則試圖把這三個特性帶到他們的設計當中。
得益於純函數的這種特點,對同一個輸入無論你重復調用多次,都會返回同樣的結果,並且沒有副作用,因此:
- 所有的調用可以使並行的。 加入你想用1到10000之間的數字去調用Add1函數,你可以把這些計算過程分配到10個不同的CPU上並行執行,由於所有的調用都是沒有副作用的,所以你不需要使用命令式語言中的鎖機制就能完成任務;
- 懶加載成為現實,你可以在真正需要結果的時候去執行計算,因為你可以保證在任何時刻對同一個輸入返回的結果總是相同的
- 你可以對函數的計算結果進行緩存,因為相同的輸入總能得到相同的結果,所以可以輕而易舉的對函數增減緩存功能
後續文章即將發出,請想了解函數式編程的童鞋支持並關註。
函數式編程介紹