天天看點

函數式程式設計初探

不僅最古老的函數式語言lisp重獲青春,而且新的函數式語言層出不窮,比如erlang、clojure、scala、f#等等。目前最當紅的python、ruby、javascript,對函數式程式設計的支援都很強,就連老牌的面向對象的java、面向過程的php,都忙不疊地加入對匿名函數的支援。越來越多的迹象表明,函數式程式設計已經不再是學術界的最愛,開始大踏步地在業界投入實用。

也許繼"面向對象程式設計"之後,"函數式程式設計"會成為下一個程式設計的主流範式(paradigm)。未來的程式員恐怕或多或少都必須懂一點。

函數式程式設計初探

但是,"函數式程式設計"看上去比較難,缺乏通俗的入門教程,各種介紹文章都充斥着數學符号和專用術語,讓人讀了如墜雲霧。就連最基本的問題"什麼是函數式程式設計",網上都搜不到易懂的回答。

一、定義

  (1 + 2) * 3 - 4

傳統的過程式程式設計,可能這樣寫:

  var a = 1 + 2;   var b = a * 3;   var c = b - 4;
  var result = subtract(multiply(add(1,2), 3), 4);

這就是函數式程式設計。

二、特點

函數式程式設計具有五個鮮明的特點。

1. 函數是"第一等公民"

舉例來說,下面代碼中的print變量就是一個函數,可以作為另一個函數的參數。

  var print = function(i){ console.log(i);}; [1,2,3].foreach(print);

2. 隻用"表達式",不用"語句"

"表達式"(expression)是一個單純的運算過程,總是有傳回值;"語句"(statement)是執行某種操作,沒有傳回值。函數式程式設計要求,隻使用表達式,不使用語句。也就是說,每一步都是單純的運算,而且都有傳回值。

原因是函數式程式設計的開發動機,一開始就是為了處理運算(computation),不考慮系統的讀寫(i/o)。"語句"屬于對系統的讀寫操作,是以就被排斥在外。

當然,實際應用中,不做i/o是不可能的。是以,程式設計過程中,函數式程式設計隻要求把i/o限制到最小,不要有不必要的讀寫行為,保持計算過程的單純性。

3. 沒有"副作用"

函數式程式設計強調沒有"副作用",意味着函數要保持獨立,所有功能就是傳回一個新的值,沒有其他行為,尤其是不得修改外部變量的值。

4. 不修改狀态

上一點已經提到,函數式程式設計隻是傳回新的值,不修改系統變量。是以,不修改變量,也是它的一個重要特點。

在其他類型的語言中,變量往往用來儲存"狀态"(state)。不修改變量,意味着狀态不能儲存在變量中。函數式程式設計使用參數儲存狀态,最好的例子就是遞歸。下面的代碼是一個将字元串逆序排列的函數,它示範了不同的參數如何決定了運算所處的"狀态"。

  function reverse(string) {     if(string.length == 0) {       return string;     } else {       return reverse(string.substring(1, string.length)) + string.substring(0, 1);     }   }

由于使用了遞歸,函數式語言的運作速度比較慢,這是它長期不能在業界推廣的主要原因。

5. 引用透明

引用透明(referential transparency),指的是函數的運作不依賴于外部變量或"狀态",隻依賴于輸入的參數,任何時候隻要參數相同,引用函數所得到的傳回值總是相同的。

有了前面的第三點和第四點,這點是很顯然的。其他類型的語言,函數的傳回值往往與系統狀态有關,不同的狀态之下,傳回值是不一樣的。這就叫"引用不透明",很不利于觀察和了解程式的行為。

三、意義

函數式程式設計到底有什麼好處,為什麼會變得越來越流行?

1. 代碼簡潔,開發快速

函數式程式設計大量使用函數,減少了代碼的重複,是以程式比較短,開發速度較快。

如果程式員每天所寫的代碼行數基本相同,這就意味着,"c語言需要一年時間完成開發某個功能,lisp語言隻需要不到三星期。反過來說,如果某個新功能,lisp語言完成開發需要三個月,c語言需要寫五年。"當然,這樣的對比故意誇大了差異,但是"在一個高度競争的市場中,即使開發速度隻相差兩三倍,也足以使得你永遠處在落後的位置。"

2. 接近自然語言,易于了解

函數式程式設計的自由度很高,可以寫出很接近自然語言的代碼。

前文曾經将表達式(1 + 2) * 3 - 4,寫成函數式語言:

  subtract(multiply(add(1,2), 3), 4)

對它進行變形,不難得到另一種寫法:

  add(1,2).multiply(3).subtract(4)

這基本就是自然語言的表達了。再看下面的代碼,大家應該一眼就能明白它的意思吧:

  merge([1,2],[3,4]).sort().search("2")

是以,函數式程式設計的代碼更容易了解。

3. 更友善的代碼管理

函數式程式設計不依賴、也不會改變外界的狀态,隻要給定輸入參數,傳回的結果必定相同。是以,每一個函數都可以被看做獨立單元,很有利于進行單元測試(unit testing)和除錯(debugging),以及子產品化組合。

4. 易于"并發程式設計"

函數式程式設計不需要考慮"死鎖"(deadlock),因為它不修改變量,是以根本不存在"鎖"線程的問題。不必擔心一個線程的資料,被另一個線程修改,是以可以很放心地把工作分攤到多個線程,部署"并發程式設計"(concurrency)。

請看下面的代碼:

  var s1 = op1();   var s2 = op2();   var s3 = concat(s1, s2);

由于s1和s2互不幹擾,不會修改變量,誰先執行是無所謂的,是以可以放心地增加線程,把它們配置設定在兩個線程上完成。其他類型的語言就做不到這一點,因為s1可能會修改系統狀态,而s2可能會用到這些狀态,是以必須保證s2在s1之後運作,自然也就不能部署到其他線程上了。

多核cpu是将來的潮流,是以函數式程式設計的這個特性非常重要。

5. 代碼的熱更新

繼續閱讀