Functional programming is a programming paradigm that treats computation as the evaluation of mathematical functions and avoids state and mutable data. It emphasizes the application of functions, in contrast to the imperative programming style, which emphasizes changes in state.
函數(shù)式編程是一種編程范式,在這種編程方式中,我們更多的使用函數(shù)運算。函數(shù)運算的特點是沒有狀態(tài)和可變量,一個函數(shù)有輸入值和輸出值,運算一個函數(shù)不會產(chǎn)生任何副作用(side effect)。
與之相對的是Imperative Programming, 就是我們通常說的命令式編程。一般我們的編程方式都是命令式的,每一段程序都是指令,明確告訴計算機要做什么,運行指令的結(jié)果往往是程序狀態(tài)(state)的改變。
一個例子最好說明兩者的區(qū)別。讓我們來打印八卦的符號。對程序員來說,這是一個很簡單的作業(yè)。典型的程序會是這樣的:
--------------------------------------------------------------------------------
#1 let yinyang = ['¦';'|']
#2 for x in yinyang do
#3 for y in yinyang do
#4 for z in yinyang do
#5 printf "%c%c%c " z y x
Output:
¦¦¦ |¦¦ ¦|¦ ||¦ ¦¦| |¦| ¦|| |||
--------------------------------------------------------------------------------
這里我用的是F#語言,一種函數(shù)式編程語言,但是它也可以用命令式的方式編程。我們用嵌套的for語句打印出每一爻,我們告訴計算機,第一爻有陰陽兩種情況,第二爻有陰陽兩種情況,第三爻有陰陽兩種情況,循環(huán)后我們得到八種可能,然后打印結(jié)果。
這好像沒有任何驚奇的東西。其實這里有一個很大的問題,這個問題就是,我們在編程之前就已經(jīng)知道了結(jié)果!八卦是如何產(chǎn)生的?“太極生兩儀,兩儀生四象,四象生八卦,八卦生萬物!边@個過程并沒有在我們的程序中體現(xiàn)出來。世界的本質(zhì)是變易,生生不息,周而復(fù)始。變易有其規(guī)律,比如牛頓萬有引力定律,廣義相對論,能量守恒定律等等。數(shù)學(xué)和物理學(xué)試圖用函數(shù)的方式來描述這些規(guī)律,而我們的程序也可以以這種方式來編寫。
下面是另一種方式來生成和打印八卦符號。
--------------------------------------------------------------------------------
#1 let generate a = a |> List.collect (fun x -> ['¦'::x; '|'::x])
#2
#3 [[]] |> generate
#4 |> generate
#5 |> generate
#6 |> List.map (List.fold (fun acc x -> acc + x.ToString()) "")
#7 |> List.iter (printf "%s ")
Output:
¦¦¦ |¦¦ ¦|¦ ||¦ ¦¦| |¦| ¦|| |||
--------------------------------------------------------------------------------
第一行定義了一個函數(shù)generate,它有一個輸入值a,等號右邊是函數(shù)的定義。關(guān)鍵部分是其中的一個變換函數(shù):
fun x -> ['¦'::x; '|'::x]
這個函數(shù)從每個輸入值x生成了兩個新的元素, 這兩個新元素分別是由在x前面加上'¦'和'|'而生成的。
|> 是pipe-forward操作符(operator)。如果你熟悉Unix命令行,你對pipe應(yīng)該很了解。它的作用就是將前面的值傳送給后面的函數(shù)來處理。這其實是一種寫法上的簡化, 事實上|> 的定義是:
let (|>) x f = f x
|>的好處是可以將pipe連接起來用,程序看上去很直觀,比如
x |> f1 |> f2 |> f3
這相當于
f3(f2(f1(x)))
回到上面的程序,第三行就是“太極生兩儀”:
[[]] |> generate
第四行是“兩儀生四象”,第五行是“四象生八卦”。第六、七行是用來打印結(jié)果,在這里不是關(guān)鍵的。
這個例子中有一些語法和模塊函數(shù)細節(jié)(比如List.collect, List.map),這里暫不做詳細說明,這篇文章的目的是想說明函數(shù)式編程與命令式編程的區(qū)別。函數(shù)式編程有很明顯的特征:
1. 函數(shù)操作數(shù)據(jù),函數(shù)沒有副作用(side effect);
2. 程序由一系列函數(shù)對數(shù)據(jù)的變換構(gòu)成;
3. 沒有變量,只有輸入值和輸出值;
在第二個例子里面,我們從空([])生成了兩儀,從兩儀生成了四象,從四象生成了八卦,所有這些中間結(jié)果以及生成過程都在程序中體現(xiàn)出來。而在第一個例子里面沒有。所以說函數(shù)式編程似乎更切合宇宙運行的本質(zhì),即變易的本質(zhì)。