1. 程式人生 > >函數式編程介紹

函數式編程介紹

好處 函數 靠譜 聲明 最終 了解 class 童鞋 stack

本文將描述函數式編程的基本理論,從而讓你理解什麽是函數式編程,同時也會展示“函數式編程”和“面向對象”這兩種不同風格的編程範式之間的區別。本文是函數式編程系列的入門篇,後續文章陸續會發出。

什麽是函數

函數式編程背後的理論依據是數學,數學函數背後有一系列有意思的特點,而函數式編程語言則試圖模擬這些特性。
讓我們先來看一個簡單的數學函數:

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上並行執行,由於所有的調用都是沒有副作用的,所以你不需要使用命令式語言中的鎖機制就能完成任務;
  • 懶加載成為現實,你可以在真正需要結果的時候去執行計算,因為你可以保證在任何時刻對同一個輸入返回的結果總是相同的
  • 你可以對函數的計算結果進行緩存,因為相同的輸入總能得到相同的結果,所以可以輕而易舉的對函數增減緩存功能

後續文章即將發出,請想了解函數式編程的童鞋支持並關註。

函數式編程介紹