這裡講的函數不是一般的 method,而是把函數當成一等公民,也就是 function 是 first-class 公民。
所謂一等公民是把 function 當成與資料一樣,可以設定,可以呼叫,可以傳遞,一般資料可以處理方法,function 也都要可以。
純 OO 的語言不願意接受非 object 的東西,object 通常代表是一個東西,容納資料(或叫有狀態)與行為的東西,有時,甚至資料(狀態)的含意更為強烈一些。
Function 很顯然是用來「描述行為」的,與 OO 物件「有資料有行為」的本質,兩者格格不入。有些 pure-OO 的擁護者,一看到 function,先拒絕再說。
「什麼是closure?」
Closure有人翻作閉包,closure簡單講就是一未命名的函數 (anonymous function),再加上他所閉鎖住的變數。什麼是閉鎖?有點困難是不是,沒關係,這裡暫時把 closure 當成一個沒有名稱的函數。
「Closure有什麼用處?」
用處可是很大,我們常常在 event handling 中需要一個 function 來處理該 event。
這裡舉個例子,若我們的 AP 想要攔截 mouse click,我們就需要一個 mouse click 的 event handler,最方便的作法是撰寫一個「event 的處理 function」,然後將該 function 設定給 mouse click,當用戶真的 mouse click,就直接呼叫該 function。
這是很簡單直覺的想法,但這種想法其實已經把 function 當成第一等公民來看待。
在 C 裡面可以使用 function pointer,在一些 script 語言更方便,直接指定一個 closure 給 event handler 即可。
在一些 pure-OO 語言呢?可就沒那麼簡單了,該 function 一定要把它包裝成 object 不可,所以弄了一個怪東西來代表一個簡單的概念(函數)。這種情況在 Java 同樣出現,你需要將該 function 包裝成一個 anonymous class 的 instance 才行。
有沒有感覺這是「為了 OO 而 OO」的作法。
其實,物件是人直覺的東西,功能也是人直覺的東西。為何人要這麼堅持呢?不懂!
幾年前曾出現過 Java 是否需要 closure 的爭論,Gosling (Java 的設計者) 認為 Java 並不需要closure,提出的理由是可以使用 anonymous inner class 來取代 closure。
Gosling 沒有說錯,anonymous inner class 確實可取代一部份 closure 的功能,但只是一部份,也就是 event handling 的那部份。
這種解釋與作法讓 Java 降低立即需要 closure 的壓力,直到新語言 Ruby、Groovy、Python 的挑戰出現。
在當時要求 Java 支援 closure 呼聲滿天高的時代,Microsoft 的 Anders Hejlsberg (Delphi、C#的設計者) 也曾出來消遣一下 Java 使用 anonymous internal class 作為 event handler 的這種作法。但那時由於 Micrsoft 的 C# 與 Java 戰火正烈,Microsoft 的所有作為都被是為敵意,所以 Anders Hejlsberg 的說法,被當成政治語言解讀。實在可惜,直到現在我們還繼續使用這種 anonymous inner class 作為 event handler 的跛腳解決方式。
筆者不覺得 Gosling 不明白 function 的好處與能力,但每個語言有他的特色,相信在 Gosling 的心目中,Java 的設計哲學就是簡單,使用 anonymous inner class 來作為解決方案,應該是他衡量簡單與複雜的取捨。
一個簡單的語言,與複雜功能強大語言比較,要做到相同的功能當然顯得繁瑣。但語言的好壞與否,不應該只在於功能其大,而應該與他的目標使用者(programmer)的操控能力有關。
對一個有操控能力的 programmer,功能強大的語言是他的選擇對象。但作為一個語言設計者,若希望他的語言可以吸引大部份的 programmer,簡單也可能是一個好的方式。
也許 Gosling 是這麼堅持的,堅持簡單就是美,雖然演化到現在的 Java 也已經一點都不簡單。
另一個讓 Gosling 沒有採用 closure 的因素,可能在於實現(implementation)。
各位若瞭解 JVM 與 Java classfile 的格式,你可能會驚訝 Java 之前為支援 generic 所付出的代價。
為了支援 generic,除了有一大堆 class library 需要改寫支援 generic 外,更重要的是 Java 需要翻修最底層的架構,JVM 要改、classfile 結構要改,這個工程可是大翻修,非常冒風險。由於翻動過大,雖然到了現在,我們還可發現 generic 翻修所留的痕跡,type erasure 就是一例。
在 Java 的 generic 中,有 type erasure 的問題,讀者可不要來跟我說這是特性(feature),compiler 看到與編譯出來的不同,就叫問題。
我們以下例來說明
List lst1 = new ArrayList<String>(); List lst2 = new ArrayList<Integer>();
上面的 lst1 與 lst2 經過compile後在classfile中所呈現出來的type,兩者都是ArrayList<Object>,無法清楚分辨出來。
「但從 Java source code,我們可以清楚分辨 lst1 與 lst2 不是同一個 type 啊? Java compiler 這麼差?」
這麼說都說對了,雖然從 source code 中,我們確實可以瞭解 lst1 與 lst2 型態的不同,而且 Java compiler 有能力可以分辨出這兩者的不同(否則你試著將上例中的 lst1 設定 lst2看看,看 compiler 是否允許),但產生出來的 bytecode(放在 classfile中),就是沒辦法分辨。
並不是 Java compiler 差,導致出現這個問題,問題原因出在 classfile 的格式。Java classfile 並無法容納 generic type 中 type 參數的實際資訊,因此 Java compiler 會先移除 generic type 中 type 參數的實際資訊,再產生 bytecode 儲存到 classfile中。
Java compiler 移除 generic type 中 type 參數的實際資訊,這個動作就叫做「type erasure」。
至於為何 classfile 無法容納 generic type 中 type 參數的實際資訊?很簡單,那就是會太複雜。
要容納這些資訊的話,classfile 的格式會變成太複雜,更動的幅度會過大,以至於很多工具無法好好有效的使用 classfile,原因很簡單吧。
這個簡單的因素,到現在仍繼續震撼 Java 的世界,使用 Java 泛型的 programmer 繼續受這個決定的影響。
這個因素甚至也影響到 Scala 的 programmer(你以後會知道 Scala generic 因為 type erasure 的原因, 需要花多大力氣來修正這個問題)。
你覺得 Java 開發團隊的人不願意好好處理這個問題嗎?應該不是。但筆者悲觀的認為他們不會再處理這個問題,因為影響太大,已經變得無法處理。
對於筆者而言,筆者倒希望 closure 之前沒被 Java 採納,真正的原因是,Java 團隊的人考量「簡單與實現的困難度」所得到的最合適結果。
現在最新的 Java 發展,closure 仍是大家關注的對象,雖然進度那麼令人不滿意。
Java 的 closure 叫 Lambda Expression,雖然已經是七折八扣的鳥籠 closure,但直到 Java 7 還是不支援,現在最新的消息是要到 Java 8 才支援。
由上面解釋泛型的經驗,我們知道要在既有的架構中,加上一個大的變動並不容易,所以對 Java 演進的期望值沒那麼高,但 Oracle 也讓我們等太久了。
在舊輪子開新車不容易,既然要 Java 做改進這麼的不容易,難怪就有些人提出,乾脆 Java 或 JVM 從今不再做進一步的演進,請 Oracle 推出新語言,來滿足新需求,也許更簡單俐落。
到此,讀者應該要有這樣的認識,就算 Java 或 JVM 會繼續做修正,幅度與演進速度也不容易滿足我們的願望,與其等待 Java 的演進,不如盡早找一個與你既有的 Java 程式碼可以有效共同運作的新語言。這個新語言,筆者認為 Scala 會是一個好的選擇。
關於 function 與 closure 這個需求,在 Scala 世界中的我們,可就幸福了。函數與 closure 在 Scala中都非常完整的被支援,雖然不像純函數的語言那麼令人驚豔,但夠我們享用了。
沒有留言:
張貼留言