副作用 (計算機科學)

在計算機科學中,函數副作用(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)可以對純函數的運算結果進行緩存,運算加快速度。