理解函數語言程式設計的本質

weixin_33686714發表於2018-01-26

前言

筆者在之前曾經花時間學習了Haskell(所謂的純函數語言程式設計語言),並且在也研究過一段時間的函式式響應程式設計,也就是Functional reactive programing,總體有直觀的感覺,但最近在學習kotlin的過程中,突然對於函數語言程式設計又引起了新的思考,於是花時間去研究了一番,究竟什麼是函數語言程式設計,總結成文希望能夠幫助你理解。

一些理解存在偏差

函數語言程式設計在各個領域經常被提及,但很多文章去講函數語言程式設計都會有些偏差,並沒有抓到本質。或者說講了函式程式設計常見的寫法,但沒有講清楚究竟什麼是函數語言程式設計。只有理解了這個本質問題,才能在後續的學習中一帆風順。

(為了理解函數語言程式設計,本文後面一些名詞直接採用英文名詞進行解釋說明,因為生硬的翻譯過來,會加大理解的難度)

什麼是函式(Function)

這個問題雖然看上去很簡單,但其實並不簡單。

函式(function)這個名詞來自於數學,函式通過一個給定的值,計算出另外一個值,也就是上學時常見的f(x)。

在通常的理解中,下面程式碼裡面,f1,f2,f3通常都被叫做函式:

//虛擬碼 函式無入參,返回2
def f1(): return 2
//函式有引數x,返回 x+1
def f2(int x): return x+1
//函式無入參,無返回值,列印hello world
def f3(): print("hello world")
複製程式碼

但實際上,函式(function)和procedure是有區別的: function 通過運算返回一個值,而procedure只執行一段程式碼,沒有返回值。 這一點對於後面的理解是非常有幫助的,首先要區分出二者。

再回到上面的程式碼中,f1,f2,為function而f3為procedure。

什麼是Pure Function

Pure:純的; 單純的; 純真的; 乾淨的 我們將滿足下面兩個條件的函式稱作Pure Function:

  1. 函式不會產生side effect(no side effect)
  2. 函式滿足referential transparency這個條件 (原諒我不會翻譯這兩個名詞)

Side effect

函式呼叫後不會對外部狀態產生影響,比如下面這段程式碼中sum函式是no side effect的:

def sum(a,b): return a+b
複製程式碼

產生side effect的函式長成什麼樣呢?其實經常會寫這樣的函式:

int sum = 0
def plus(a,b){
  sum = a + b
  return sum
}
複製程式碼

plus函式除了計算引數之和以外,還改變了外部變數sum的值,我們plus這個函式產生了side effect。

常見的Side effect

  • 改變外部變數的值(上面的例子中plus函式)
  • 像磁碟中寫入資料
  • 將頁面上的一個按鈕設定為可點選,或者不可點選

前面提到function和procedure的不同點,在side effect這個角度來講,pure funcion不會產生side effect,procedure通常會產生side effect。

Referential transparency

Referential transparency means that given a function and an input value, you will always receive the same output. That is to say there is no external state used in the function.

Referential transparency means that you can replace anyexpression in the program with the result of evaluating that expression (or vice versa) without changing the meaning of the program.

  • 滿足Referential Transparency的函式可以將可以將用函式計算的結果替換表示式本身,而不影響程式的邏輯。
  • 給定指定的引數,在任何時候返回的值都是相同的。不受其他外部條件影響。

兩者說的意思是一樣的,只是表達的角度是不同的

舉個滿足RT的例子

def f(): return 2

print(f() + f())
print(2)
複製程式碼

下面這段程式碼中的f()是滿足RT的函式,按照上面的解釋,我們可以將f()的結果也就是2替換掉f(),不會影響程式本身的邏輯:

def f(): return 2

print(2 + f())
print(2)
複製程式碼

或者這樣替換:

def f(): return 2

print(f() + 2)
print(2)
複製程式碼

從另一個角度說,f()這個函式無論在什麼時候呼叫,返回的值都是一樣的,不會發生改變(沒有外部條件影響)

舉個不滿足RT的例子

int counter = 0

def f(x){
  counter += 1
  return x + counter
}
複製程式碼

這個例子中,f(x)這個函式不滿足RT 下面的程式碼中,當我們用f(1)的計算結果一次替換程式碼中f(1)本身時,程式的邏輯是錯誤的:

//原始的程式碼執行結果是:3
f(1) + f(1)

//把f(1)的結果1替換進來,下面函式執行的結果是:2
f(1) + 1

//同樣,得到2
1 + f(1)

//得到2
1 + 1
複製程式碼

我們不能用執行的結果替換函式本身,

換個角度,下面兩行程式碼執行的結果也不同

f(1) + f(1)
2 * f(1)
複製程式碼

雖然入參都為1,但f(1)在不同時候呼叫得到的結果不同,因此f不滿足RT這個條件

回到pure function

理解了side effect 和 referential transparency的含義,我們再來重溫pure function的定義,就很好理解了:

  • No side effect
  • Referential transparency

滿足這兩個條件的函式,稱之為pure function

什麼是函數語言程式設計

理解了pure function之後,我們回到什麼是functional programing,就會有種豁然開朗的感覺:

Functional programming is about writing pure functions

函數語言程式設計最大限度的寫pure function,讓函式最大限度的減少side effect,並且保證函式在任何時候傳遞相同引數時,得到的結果都相同。

有什麼好處

當我們按照函數語言程式設計的思想編寫程式時,程式具有天然的模組屬性,因為實現的函式為pure function,你可以任意組合這些pure function。

pure function不會產生side effect,不需要對外部的狀態產生顧慮。

其他好處在很多文章中都有提到,後續有時間筆者再來分析。:-D

總結

希望通過前面幾個核心概念的講解,能夠讓你明白函數語言程式設計的本質,如果有任何文中寫的有偏差的地方,歡迎討論,?

相關文章