1. 程式人生 > >函數語言程式設計和指令式程式設計

函數語言程式設計和指令式程式設計

突然直接明白了他們的含義。

所謂指令式程式設計,是以命令為主的,給機器提供一條又一條的命令序列讓其原封不動的執行。程式執行的效率取決於執行命令的數量。因此才會出現大O表示法等等表示時間空間複雜度的符號。

而函式式語言並不是通常意義上理解的“通過函式的變換進行程式設計”。注意到純的函式式語言中是沒有變數的(沒有可以改變的東西,所有的東西在定義以後就都是不變的),那麼這樣的東西有什麼好處呢?就比如,如果所有的東西都是不變的,那麼我們又怎麼進行程式設計呢?

實際上,我們在函數語言程式設計中進行構建的是實體與實體之間的關係。在這種意義上,lisp雖然不是純粹的函數語言程式設計,但是也算是函數語言程式設計一員。使用這種定義,大多數提供了原生的list支援的指令碼語言也可以算混合了函式式語言的功能,但是這不是函式式語言的精髓。知其然,還要知其所以然。我們既然已經有了精確自然的指令式程式設計,又為什麼還需要函數語言程式設計呢?我們舉個小例子。

int fab(int n) {
return n == 1 || n == 2 ? 1 : fab(n - 1) + fab(n - 2);
}

這是用C語言寫的求斐波那契數列的第N項的程式,相應的Haskell程式碼是這樣的:

fab :: (Num a) => a -> a
fab n = if n == 1 || n == 2 then 1 else fab(n - 1) + fab(n - 2)


看上去差不多對不對?但是這兩個程式在執行的效率方面有著天差地別的差距。為什麼呢?C語言是標準的指令式程式設計語言。因此對於你寫下的每一行語句,C程式會原封不動地機械地去執行。如果想效率提高,你必須自己去分析程式,去人工地減少程式中執行的語句的數量。具體到這個C程式,我們注意到在每次函式呼叫時,都會產生兩個新的函式呼叫。這時,實際產生的函式呼叫的數目是指數級別的!比方說,我們寫fab(5),實際的執行結果是:

fab(5)
fab(4)
fab(3)
fab(2)
fab(1)
fab(2)
fab(3)
fab(2)
fab(1)

我們看到,fab(3)被求值了兩遍。為了計算fab(5),我們實際執行了8次函式呼叫。

那麼函式式語言呢?我們說過,函式式語言裡面是沒有變數的。換句話說,所有的東西都是不變的。因此在執行fab(5)的時候,過程是這樣的:

fab(5)
fab(4)
fab(3)
fab(2)
fab(1)
fab(3)

總共只有五次應用。注意我說的是應用而不是呼叫。因為函式式語言裡的函式本意並不是命令式語言裡面的“呼叫”或者“執行子程式”的語義,而是“函式與函式之間的關係”的意思。比如fab函式中出現的兩次fab的應用,實際上說明要計算fab函式,必須先計算後續的兩個fab函式。這並不存在呼叫的過程。因為所有的計算都是靜態的。haskell可以認為所有的fab都是已知的。因此實際上所有遇到的fab函式,haskell只是實際地計算一次,然後就快取了結果。

本質上,這代表了我們提供給函式式語言的程式其實並不是一行一行的“命令”,而只是對資料變換的說明。這樣函式式語言可以深入這些說明中,尋找這些說明中冗餘的共性,從而進行優化。這就是函式式語言並不需要精心設計就會比命令式語言高效的祕密。命令式語言當然也可以進行這種優化,但是因為命令式語言是有邊界效應的。而且大部分情況下都是利用邊界效應進行計算,因此很難推廣這種優化,只有少數幾種窺孔優化能取得效果。

放到這個例子上,因為本質上我們兩次的fab應用是重疊的。haskell發現了這個特點,於是將兩次fab的結果快取下來(注意,能快取結果的必要條件是這個函式返回的值是不會變的!而這是函式式語言主要的特性)。如果後續的計算需要用到這兩次fab的結果,就不需要再次重複計算,而只是直接提取結果就可以了。這就是上面幾乎完全一樣的兩個程式效率相差如此之大的主要原因。

函式式語言有這樣的優勢,那麼函式式語言有沒有缺陷呢?當然是有的。函式式語言不如命令式語言那麼純粹。和機器一一對應,這在某些情形下會導致更差的效率和更低的開發效率。對計算模型不斷深入的瞭解會縮短這兩者之間的差距。然而,一定要注意命令式語言是植根於馮·諾依曼體系的,一旦新的體系產生了革命性的改變。那麼命令式語言就不會再適用,而只能通過模擬的方法進行執行,到那個時候,函式式語言和命令式語言的地位就會完全顛倒過來,當然這並不是我們目前需要考慮的問題,但是在現在稍微瞭解一點函式式語言程式設計的思想是十分重要的。

日常生活中的函數語言程式設計:
- C++模板超程式設計
- C/C++預處理超程式設計
- M4巨集程式設計
- Python/Perl/Ruby的列表操作
- Vimscript的某些操作