【轉】函數式編程(續)

以前已經有一篇文章是講了函數式編程javascript

http://www.cnblogs.com/charlesblc/p/6110484.htmlhtml

 

今天又翻到了ruanyifeng的文章java

http://www.ruanyifeng.com/blog/2012/04/functional_programming.htmlexpress

正好複習一下。編程

 

誕生50多年以後,函數式編程(functional programming)開始得到愈來愈多的關注。閉包

不只最古老的函數式語言Lisp重獲青春,並且新的函數式語言層出不窮,好比Erlang、clojure、Scala、F#等等。目前最當紅的Python、Ruby、Javascript,對函數式編程的支持都很強,就連老牌的面向對象的Java、面向過程的PHP,都忙不迭地加入對匿名函數的支持。愈來愈多的跡象代表,函數式編程已經再也不是學術界的最愛,開始大踏步地在業界投入實用。併發

 

注:Java中的匿名函數是 Java8開始支持的。指的就是Lambda表達式,又稱爲閉包或匿名函數。ide

在C語言中的概念相似於一個函數指針,這個指針能夠做爲一個參數傳遞到另一個函數中。模塊化

因爲Java是相對較爲面向對象的語言,一個Java對象中能夠包含屬性和方法(函數),方法(函數)不能孤立於對象單獨存在。這樣就產生了一個問題,有時候須要把一個方法(函數)做爲參數傳到另一個方法中的時候(好比回調功能),就須要建立一個包含這個方法的接口,傳遞的時候傳遞這個接口的實現類,通常是用匿名內部類的方式來函數式編程

以下面代碼,首先建立一個Runnable的接口,在構造Thread時,建立一個Runnable的匿名內部類做爲參數:

new Thread(new Runnable() {  
    public void run() {  
            System.out.println("hello");  
        }  
}).start();  
相似這種狀況的還有swing中button等控件的監聽器,以下面代碼所示,建立該接口的一個匿名內部類實例做爲參數傳遞到button的addActionListener方法中。
public interface ActionListener { void actionPerformed(ActionEvent e); } button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { ui.dazzle(e.getModifiers()); } });

這樣的代碼的缺點是有代碼笨重,可讀性差,不能引用外面的非final的變量等。lambda表達式就是爲了解決這類問題而誕生的。

在介紹Java8中的Lambda表達式以前,首先介紹一個概念叫「函數式接口」(functional interfaces)。對於任意一個Java接口,若是接口中只定義了惟一一個方法,那麼這個接口就稱之爲「函數式接口」。好比JDK中的ActionListener、Runnable、Comparator等接口。

 

先來看一下Java8中的lambda表達式的使用示例,建立一個線程:

new Thread(() -> {System.out.println("hello");}).start();    

能夠看到這段代碼比上面建立線程的代碼精簡了不少,也有很好的可讀性。

     () -> {System.out.println("hello");}  就是傳說中的lambda表達式,等同於上面的new Runnable(), lambda大致分爲3部分:

     1.最前面的部分是一對括號,裏面是參數,這裏無參數,就是一對空括號

     2.中間的是 -> ,用來分割參數和body部分

     3.是body部分,能夠是一個表達式或者一個語句塊。若是是一個表達式,表達式的值會被做爲返回值返回;若是是語句塊,須要用return語句指定返回值。

以下面這個lambda表達式接受一個整形的參數a,返回a的平方

(int a) -> a^2   

等同於

(int a) -> {return a^2;}  

更多的例子:

(int x, int y) -> x + y  
  
() -> 42  
  
(String s) -> { System.out.println(s); }  

建立一個FileFilter,文件過濾器:
FileFilter java = (File f) -> f.getName().endsWith(".java")  

建立一個線程:
new Thread(() -> {  
  //do sth here...  
}).start()  

建立一個Callable:
Callable<String> c = () -> "done";  

建立一個String的比較器:
Comparator<String> c = (s1, s2) -> s1.compareToIgnoreCase(s2);

並且lambda表達式能夠賦值給一個變量:
Comparator<String> c;  
c = (String s1, String s2) -> s1.compareToIgnoreCase(s2);  

還能夠做爲方法的返回值:
public Runnable toDoLater() {  
  return () -> {  
    System.out.println("later");  
  };  
}  

從上面能夠看到,一個lambda表達式被做爲一個接口類型對待,具體對應哪一個接口,編譯器會根據上下文環境推斷出來,以下面的lambda表達式就表示一個ActionListener.

ActionListener l = (ActionEvent e) -> ui.dazzle(e.getModifiers());  

這有可能會形成一個表達式在不一樣的上下文中被做爲不一樣的類型,以下面的這種狀況,儘管兩個表達式是相同的,上面的表達式被推斷爲Callable的類型,下面的會被推斷爲PrivilegedAction類型。

Callable<String> c = () -> "done";  
PrivilegedAction<String> a = () -> "done";  

那麼編譯器是根據哪些由於決定一個表達式的類型呢?

     若是一個表達式被推斷爲是T類型的,須要知足如下4個條件:

     1.T是函數式接口類型(只聲明惟一一個方法)

     2.表達式和T中聲明的方法的參數個數一致,參數類型也一致

     3.表達式和T中聲明的方法的返回值類型一致

     4.表達式和T中聲明的方法拋出的異常一致

有了這個準則,上面的疑問就迎刃而解了。(注:其實我以爲沒有必要糾結表達式的類型)

以上參考:http://www.iteye.com/topic/1127931

 

如下,繼續學習:http://www.ruanyifeng.com/blog/2012/04/functional_programming.html

 

也許繼"面向對象編程"以後,"函數式編程"會成爲下一個編程的主流範式(paradigm)。

上面文章主要參考了Slava Akhmechet的"Functional Programming For The Rest of Us"

 

"函數式編程"是一種"編程範式"(programming paradigm),也就是如何編寫程序的方法論。它屬於"結構化編程"的一種,主要思想是把運算過程儘可能寫成一系列嵌套的函數調用。舉例來講,如今有這樣一個數學表達式:

(1 + 2) * 3 - 4

函數式編程要求使用函數,咱們能夠把運算過程定義爲不一樣的函數,而後寫成下面這樣: var result = subtract(multiply(add(1,2), 3), 4);

 

函數式編程具備五個鮮明的特色。

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

所謂"第一等公民"(first class),指的是函數與其餘數據類型同樣,處於平等地位,能夠賦值給其餘變量,也能夠做爲參數,傳入另外一個函數,或者做爲別的函數的返回值。

舉例來講,下面代碼中的print變量就是一個函數,能夠做爲另外一個函數的參數。
  var print = function(i){ console.log(i);};

  [1,2,3].forEach(print);

 

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

"表達式"(expression)是一個單純的運算過程,老是有返回值;"語句"(statement)是執行某種操做,沒有返回值。函數式編程要求,只使用表達式,不使用語句。也就是說,每一步都是單純的運算,並且都有返回值。

緣由是函數式編程的開發動機,一開始就是爲了處理運算(computation),不考慮系統的讀寫(I/O)。"語句"屬於對系統的讀寫操做,因此就被排斥在外。

固然,實際應用中,不作I/O是不可能的。所以,編程過程當中,函數式編程只要求把I/O限制到最小,不要有沒必要要的讀寫行爲,保持計算過程的單純性。

 

3. 沒有"反作用"

所謂"反作用"(side effect),指的是函數內部與外部互動(最典型的狀況,就是修改全局變量的值),產生運算之外的其餘結果。

函數式編程強調沒有"反作用",意味着函數要保持獨立,全部功能就是返回一個新的值,沒有其餘行爲,尤爲是不得修改外部變量的值。

 

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),指的是函數的運行不依賴於外部變量或"狀態",只依賴於輸入的參數,任什麼時候候只要參數相同,引用函數所獲得的返回值老是相同的。

有了前面的第三點和第四點,這點是很顯然的。其餘類型的語言,函數的返回值每每與系統狀態有關,不一樣的狀態之下,返回值是不同的。這就叫"引用不透明",很不利於觀察和理解程序的行爲。

 

3、意義

函數式編程到底有什麼好處,爲何會變得愈來愈流行?

 

1. 代碼簡潔,開發快速

函數式編程大量使用函數,減小了代碼的重複,所以程序比較短,開發速度較快。

Paul Graham在《黑客與畫家》一書中寫道:一樣功能的程序,極端狀況下,Lisp代碼的長度多是C代碼的二十分之一。

若是某個新功能,Lisp語言完成開發須要三個月,C語言須要寫五年。"固然,這樣的對比故意誇大了差別,可是"在一個高度競爭的市場中,即便開發速度只相差兩三倍,也足以使得你永遠處在落後的位置。"

 

2. 接近天然語言,易於理解

 

3. 更方便的代碼管理

函數式編程不依賴、也不會改變外界的狀態,只要給定輸入參數,返回的結果一定相同。所以,每個函數均可以被看作獨立單元,頗有利於進行單元測試(unit testing)和除錯(debugging),以及模塊化組合。

 

4. 易於"併發編程"

函數式編程不須要考慮"死鎖"(deadlock),由於它不修改變量,因此根本不存在"鎖"線程的問題。沒必要擔憂一個線程的數據,被另外一個線程修改,因此能夠很放心地把工做分攤到多個線程,部署"併發編程"(concurrency)。

多核CPU是未來的潮流,因此函數式編程的這個特性很是重要。

 

5. 代碼的熱升級

函數式編程沒有反作用,只要保證接口不變,內部實現是外部無關的。因此,能夠在運行狀態下直接升級代碼,不須要重啓,也不須要停機。Erlang語言早就證實了這一點,它是瑞典愛立信公司爲了管理電話系統而開發的,電話系統的升級固然是不能停機的

 

(完)

相關文章
相關標籤/搜索