編程范式(PRogramming paradigm)
編程范式指我們在編寫程序解決問題的思路和視角。它提供了同時也決定了程序員對程序運行的看法。計算機編程中存在許多編程范式,如命令式編程、聲明式編程、面向?qū)ο缶幊桃约敖Y(jié)構(gòu)化編程等等。其中面向?qū)ο缶幊谭妒秸J為程序是由一系列相互作用的對象組成,而結(jié)構(gòu)化編程范式認為程序采用子程序、代碼區(qū)塊、for循環(huán)以及while循環(huán)等結(jié)構(gòu)組成。下面主要說明本篇文章將要講到的命令式編程范式和聲明式編程范式。
1)命令式編程(Imperative):
強調(diào)程序代碼模擬電腦運行過程,強調(diào)“先做什么”、“再做什么”。如果我們要計算“2*3+1”,我們編寫代碼時先計算2*3存入臨時變量,再計算該臨時變量與1的和。命令式編程是當前主流編程范式,我們編寫的代碼幾乎都屬于命令式編程范式。
2)聲明式編程(Declarative):
強調(diào)程序代碼模擬人腦計算過程,強調(diào)“最終要什么”,相比命令式編程范式來講,它更看重結(jié)果而非過程。聲明式編程范式更接近人類思想,它的思考層面要高于命令式編程。
下圖顯示了命令式編程范式與聲明式編程范式的區(qū)別:
圖1
注:各種編程范式之間并非都是對立的,很多范式是從不同角度來劃分的。如面向?qū)ο缶幊谭妒酵瑫r也屬于命令式編程范式。當然,本篇文章講到的“命令式編程范式”和“聲明式編程范式”兩者是對立的。
聲明式編程范式
聲明式編程范式常見有以下兩種(最常見):
1)領(lǐng)域特定語言(Domain Specific Language,DSL):
名字很陌生,但是我們卻經(jīng)常在用。如SQL、CSS以及正則表達式等等。這些語言只在特定領(lǐng)域起作用,并且使用這些語言時,我們大多數(shù)時候是在寫“陳述、聲明”的語句。如“select * from tb”,我們只關(guān)心我們要的結(jié)果,而不用去關(guān)系具體實現(xiàn)。
2)函數(shù)式編程(Functional Program,F(xiàn)P):
函數(shù)式編程是我們要討論的重點。既然它屬于聲明式編程范式,那么它也應(yīng)該強調(diào)結(jié)果(What)而非過程(How)。沒錯,函數(shù)式編程不同于常見的命令式編程,它不關(guān)心計算機具體的實現(xiàn)過程,而僅僅注重問題結(jié)果。
函數(shù)式編程(Functional Program):
網(wǎng)上關(guān)于“函數(shù)式編程”的解釋有很多,但大多數(shù)都比較模糊抽象。維基百科上對函數(shù)式編程的解釋是“In computer science, functional programming is a programming paradigm that treats computation as the evaluation of mathematical functions and avoids state and mutable data”,翻譯成中文就是“函數(shù)式編程是一種編程范式,它將計算機運算看作是數(shù)學(xué)中函數(shù)的計算,并且避免了狀態(tài)以及變量的概念”。這是個什么意思呢?很多文章分別從函數(shù)式編程的幾個特點上做出了解釋,比如“函數(shù)是第一公民”、“高階函數(shù)(Higher Order Function)”、“無狀態(tài)性(No State)”、“無副作用性(Side-Effect)”、“易于并行開發(fā)”以及“惰性求值”等等。但是我覺得這些都只是函數(shù)式編程的特點或者說是優(yōu)點,并沒有實質(zhì)上解釋出“函數(shù)式編程”與普通命令式編程的區(qū)別。我認為要搞清楚函數(shù)式編程,必須先認清“函數(shù)”的概念。沒錯,雖然我們自認為我們比較熟悉“函數(shù)”(或者叫“方法”,本文不區(qū)分這兩者的區(qū)別),但是我們真的熟悉它們嗎?
注:以后博客將依次介紹“函數(shù)式編程”以上幾種特點。
編程函數(shù)和數(shù)學(xué)函數(shù):
第一次了解“函數(shù)”的概念應(yīng)該是我們讀中學(xué)時,“y=x+1”在平面坐標系中是一條直線,到后來(不知道哪年級)學(xué)習(xí)了二次函數(shù),“y=x^2+2*x+1”在平面坐標系中是一條拋物線。當時學(xué)習(xí)函數(shù)時知道以下知識點:
1)函數(shù)是一種映射,自變量經(jīng)由一種映射關(guān)系變換后,得到因變量(函數(shù)值);
2)對于每個自變量,均能、有且僅有一個因變量與之對應(yīng),這是函數(shù)的確定性。也就是說,給定一個自變量,任何時候函數(shù)值都唯一;
那么,到大學(xué)學(xué)習(xí)編程后(本人讀大學(xué)才開始學(xué)習(xí)編程),我們在程序中又遇見了“函數(shù)”,很熟悉的感覺。但是它和數(shù)學(xué)中的函數(shù)有什么關(guān)聯(lián)呢?也就是說,數(shù)學(xué)思想與我們編程思想是否有關(guān)聯(lián)?如果以我們目前寫C#、java、C++等代碼來看,它們幾乎沒有關(guān)系,因為我們程序中的函數(shù)可以沒有參數(shù)(數(shù)學(xué)函數(shù)中的“自變量”),也可以沒有返回值(數(shù)學(xué)函數(shù)中的因變量),就算一個函數(shù)有返回值,那么給定參數(shù),調(diào)用函數(shù)后每次運行結(jié)果也可能不一樣。以上這些均不能滿足數(shù)學(xué)函數(shù)的概念。其實出現(xiàn)“兩種函數(shù)幾乎無關(guān)系”的現(xiàn)象很容易理解,數(shù)學(xué)描述的是人類思維過程,而我們(目前大部分人)編寫的程序代碼描述的是計算機運行過程。在數(shù)學(xué)家與程序員之間早已產(chǎn)生了溝通障礙,比如下圖:
圖2
如上圖所示,“X=X+1”這種表達式如果從數(shù)學(xué)角度來看,幾乎是不可成立的,讓任何一個沒有學(xué)過編程的人去看這個表達式,TA都會以為你寫的是錯的,他們只認“Y=X+1”。原因很簡單,在程序中,符號可以代表變量,而變量表示一個內(nèi)存單元,該內(nèi)存處的值可以被重寫(賦值);而在數(shù)學(xué)中,符號永遠只是符號,等號“=”兩邊表示等價關(guān)系,“Y=X+1”表示Y與X+1是等價的,Y僅僅是X+1的一個代替符號。
同理,函數(shù)也一樣。數(shù)學(xué)中的函數(shù)僅僅描述一種“映射關(guān)系”,給定一個自變量,我們可以得到一個因變量,僅此而已。而程序中的函數(shù)更多的時候扮演的是一種“功能”角色,它能夠完成指定任務(wù)。當然,如果程序中一個函數(shù)包含參數(shù),并且能夠返回值,那么它完全可以模擬數(shù)學(xué)函數(shù)。下面使用C#編寫一個委托,它代表數(shù)學(xué)中的一個一元函數(shù):
1 public delegate double Function1X(double x);
如上代碼所示,委托簽名中包含一個double類型參數(shù),并且返回一個double類型返回值。數(shù)學(xué)中的“f(x)=x^2+2*x+1”可以使用C#編寫以下函數(shù):
1 public double f(double x)2 {3 return Math.Pow(x,2) + 2*x + 1;4 }
函數(shù)f(x)在x=2處的值調(diào)用代碼:f(2);。或者使用Lambda表達式:
x => Math.Pow(x,2) + 2*x + 1;
程序中的函數(shù)接收一個double類型參數(shù),經(jīng)過映射關(guān)系,返回一個double類型的返回值,它與“f(x)=x^2 + 2*x +1”對應(yīng)。那么數(shù)學(xué)函數(shù)中的二元函數(shù)在程序中怎樣表示呢?很簡單,二元函數(shù)包含兩個自變量,我們只需要為程序中函數(shù)定義兩個參數(shù)即可:
1 public delegate double Function2XY(double x,double y);
如上代碼所示,委托簽名中包含兩個double類型參數(shù),并且返回一個double類型返回值。
從上面的介紹可以看出,如果將程序中函數(shù)做一些限制,那么它就可以模擬數(shù)學(xué)中的函數(shù)了:
1)每個函數(shù)必須包含輸入?yún)?shù)(作為自變量);
2)每個函數(shù)必須有返回值(作為因變量);
3)無論何時,給定參數(shù)調(diào)用函數(shù)時,返回值必須一致。
上面第三條限制是為了滿足函數(shù)的“確定性”,該條限制要求程序中的函數(shù)執(zhí)行期間不能依賴于外界因素,也不要影響外部環(huán)境。換句話說,它在執(zhí)行期間與外界是隔絕的。我們將滿足以上條件的函數(shù)稱為“純函數(shù)(Pure Function)”。純函數(shù)與外界交互只有一條渠道——傳入?yún)?shù)與返回值。純函數(shù)也不讀取/改變?nèi)肿兞俊oIO操作等。
圖3
純函數(shù)是程序代碼模擬數(shù)學(xué)函數(shù)的基礎(chǔ)。理論上講,函數(shù)式編程中,函數(shù)是第一公民的同時,所有函數(shù)也都應(yīng)該屬于“純函數(shù)”。到此,我們再回過頭看一下維基百科上對“函數(shù)式編程”的解釋:函數(shù)式編程是一種編程范式,它將計算機運算看作是數(shù)學(xué)中函數(shù)的計算,并且避免了狀態(tài)以及變量的概念。很顯然,函數(shù)式編程向數(shù)學(xué)驗算靠攏,使用一種平時正常的數(shù)學(xué)思維去解決問題。
注:函數(shù)式編程是基于“lambda驗算(Lambda Calculus)”的,它并不屬于“圖靈機”理論范疇。我沒搞清楚lambda驗算,所以本文并沒詳細提到。看到Lambda很容易讓我們想到C#3.0中引入的Lambda表達式,這不是偶然。C# 3.0之后開始支持“函數(shù)式編程”,后面文章將會講到。
(未完待續(xù))
新聞熱點
疑難解答