副作用 (電腦科學)

在電腦科學中,函數副作用(side effect)指當調用函數時,除了返回可能的函數值之外,還對主調用函數產生附加的影響。例如修改全域變數(函數外的變數),修改參數,向主調方的終端、管道輸出字符或改變外部存儲資訊等。

在某些情況下函數副作用會給程式設計帶來不必要的麻煩,給程式帶來十分難以查找的錯誤,並降低程式的可讀性與可移植性。嚴格的函數式語言要求函數必須無任何副作用,但功能性靜態函數本身的目的正是產生某些副作用。在生命科學中,副作用往往帶有貶義,但在電腦科學中,副作用有時正是「主要作用」。

下面是函數的副作用相關的幾個概念,純函數非純函數參照透明性

純函數

純函數(英語:Pure Function)——輸入輸出資料串流全是顯式(英語:Explicit)的。

顯式的意思是,函數與外界交換資料只有一個唯一渠道——參數和回傳值;函數從函數外部接受的所有輸入資訊都通過參數傳遞到該函數內部;函數輸出到函數外部的所有資訊都通過回傳值傳遞到該函數外部。

非純函數

如果一個函數通過隱式(英語:Implicit)方式,從外界獲取資料,或者向外部輸出資料,那麼,該函數就不是純函數,叫作非純函數(英語:Impure Function)。

隱式的意思是,函數通過參數和回傳值以外的渠道,和外界進行資料交換。比如,讀取全域變數,修改全域變數,都叫作以隱式的方式和外界進行資料交換;比如,利用 I/O API(輸入輸出系統函式庫)讀取設定文件,或者輸出到文件,打印到螢幕,都叫做隱式的方式和外界進行資料交換。

參照透明性

無副作用是參照透明性英語Referential transparency(英語:Referential Transparent)的必要非充分條件。參照透明意味着一個表達式(例如一次函數調用)可以被替換為它的值。這需要該表達式是純的,也就是說該表達式必須是完全確定的(相同的輸入總是導致相同的輸出)而且沒有副作用。

範例

f(x) { 
    return x + 1 
  }

f(x)函數就是純函數

  a = 0 
  q(x) { 
    b = a 
  }

q(x)訪問了函數外部的變數。q(x)是非純函數

  p(x) { 
    print“hello” 
  }

p(x)通過I/O API輸出了一個字符串。p(x)是非純函數。

  c(x) { 
    // 假設readConfig()函數為I/O API的函數
    data = readConfig() // 读取配置文件 
  }

c(x)通過I/O API讀取了設定文件。c(x)是非純函數。

函數內部有隱式(Implicit)的資料串流,這種情況叫做副作用(Side Effect)。上述的I/O,外部變數等,都可以歸為副作用。因此,純函數的定義也可以寫為「沒有副作用的函數」。

I/O API 可以看作是一種特殊的全域變數。文件、螢幕、資料庫等輸入輸出結構可以看作是獨立於運行環境之外的系統外全域變數,而不是應用程式自己定義的全域變數。

特殊的函數副作用

上述只討論了一般的情況,還有一種特殊的情況,我們沒有討論。有些函數的參數是一種 In/Out 作用的參數,即函數可能改變參數裏面的內容,把一些資訊通過輸入參數,夾帶到外界。這種情況,嚴格來說,也是副作用。也是非純函數。 比如下面的函數。

  process(context) {
    a = context.getInfo()
    result = calculate(a)
    context.setResult(result)
  }

純函數的優點

純函數的好處主要有幾點:

  • 無狀態,執行緒安全,不需要執行緒同步。
  • 純函數相互調用組裝起來的函數,還是純函數。
  • 應用程式或者運行環境(Runtime)可以對純函數的運算結果進行快取,運算加快速度。