大辯論:Java不是C++ 閉包讓Java更簡單
原創【51CTO精選譯文】編者按:在上個月舉行的Devoxx大會上,Sun宣布Java 7中將包含簡單的閉包特性。這與之前一年Sun的宣布完全相反——當時他們說Java將不會考慮閉包,因為Java不需要。Java社區對此是有人欣喜有人怒,不過,到底有什么好爭論的呢?一位對C++和Java十分了解,一直關注編程語言的資深開發者Marek Krj在最近的一篇博文中詳細的分析了Java需要閉包的理由,并引申C++和C#的一些情況做為自己的論據。以下是Marek的討論:
#t#多年前,Sun這樣描述微軟對Java的擴展提案:
Visual J++的許多優點——類型安全、面向對象、且易于組件互聯(component interconnection)——只是對Java對象模型的安全性和靈活性的繼承………
方法綁定(Bound)引用根本沒有必要,它們并不屬于Java編程語言的一個部分,因此它們也不能被標準的編譯器接受。此外,這些做法會讓Java語言損失掉簡便性和統一性(unity)。
如此等等,這都是些營銷時所說的胡言亂語。我不知道還有什么比這更惱人的:1.自詡面向對象的正統地位;2.自滿:“我們更理解Java”。但無論如何,James Gosling(Java之父本人)也很可能會常說他希望閉包(closure)包含在Java中,而不是匿名內部類(anonymous inner classes);3. 對于如何將函數作為Java語言的頭等公民(first class citiciens)那樣包含進去,沒有一個完整的提案!這就是他們所謂的“根本沒有必要”的論據……
圍繞著閉包的思想存在著相當大的爭議,這有點像我們最近討論過的關于C++ concepts特性的爭論:人們認為它不必要,而且太復雜了(51CTO編者注:作者此處意為今年7月下旬,C++標準委員會確定將concepts特性從C++0x中移除一事。C++編程語言的作者Bjarne Stroustrup為此聲稱C++0x已死,將希望放在新一代C++標準上。)。由于在C++0x中,我們可以使用lambda函數,這沒引起多少爭議,因此Java社區對于閉包的這一爭論激起了我的興趣。來啊,為什么不引入閉包呢?甚至C#都包含了一些閉包!
那么,什么是閉包(Closure)呢?在這篇博文中,我們把它認為是類似于C++(或Python和Groovy)中lambda那樣的函數,即一個匿名的、獨立式的函數,比如下面這種:
- auto print = [] (int x) { cout << x; }
現在,讓我們用Java語言來表示這一概念。
1.提案
那么,讓我們來看一看這三個提案。
a) BGGA(源自Bracha、Gafter、Gosling 和 Ahe的首字母)
這是最雄心勃勃的一個提案。它引入了一個新的語言類型:函數類型,以及它的一個全新的標記方法(notation),這讓我想起了的Haskell或lambda函數的演算類型(calculus types):
{T => U} 表示一個從T到U的函數{T => U => V} (或者這種形式{T,U => V}?) 表示一個從 T和U到V的函數
我認為,這種標記方法是許多Java從業者不太喜歡此提案的原因之一。另一個原因顯然歸因于非局部(nonlocal)環境語句(return ,break等)。新問題再次出現,非局部是指那個局部?嗯,舉個例子來說吧,這意味著返回語句將不會從閉包返回,而是從調用該閉包的區域內返回。它們不是綁定在閉包上,而是綁定在一個環境中。為什么這會是一件好事呢?親愛的讀者,下一節將為您詳細說明。
一個使用的例子:
- {Integer => void} print = { Integer x => System.out.println(x); }
b) CICE (簡明實例創建方式,Concise Instance Creation Expressions)
這是三個提案中最簡單的一個。基本上,它只起到在語法上不斷定義匿名類的作用而已,沒有其他的用處。沒有第一類函數(first class functions)!沒有晦澀難懂的函數編程理論!
以下是一個例子:
- public interface IPrint { public void invoke(Integer x);} // exacltly 1 method!
- Print print = IPrint(Integer x) { System.out.println(x); }
雖然CICE是最簡單的提案,但我認為它也是最煩人的一個提案:你必須經常返回去查詢一些接口的定義,真是太差勁了!
c) FCM(第一類方法,First Class Methods)
這是上面兩個方案的折衷:它引入了第一類函數,但其閉包返回語句中并沒有包含非局部綁定,而且去掉了讓人討厭的lambda形式符號。(51CTO編者注:FCM是此次Sun宣布通過的提案,詳見Devoxx大會的相關報道。不過,作者這篇文章寫于Devoxx之前,當時對此還并不知曉。)
以下是一個例子:
- #(void(Integer)) print = #(Integer x) { System.out.println(x); }
從以上三類的語法構成上來說,我最喜歡的BGGA提案,因為你能夠用熟悉的代碼塊(code-block)方式來編程,而不必使用FCM的hash和CICE的接口標簽。
- #(void(Integer)) print = #(Integer x) { System.out.println(x); }
2.爭論
說得客氣一點,我有時覺得這場辯論有點幼稚。下面是典型的辯點:
◆正方觀點:如果沒有閉包,我將永遠不會用Java寫哪怕一行代碼,我會轉而使用Scala進行編程。
◆反方觀點:如果引入閉包,我將會崩潰掉……雖然我不會轉向其它語言,但我會因此而怨恨你!
得了吧,各位!只不過是一個語言的特性而已。如果你不喜歡的話,你不使用它不就得了嗎?而且,選擇什么語言進行開發,是由項目經理決定,而不是由開發人員說了算!如果經理們認為一個語言能夠帶來某種好處,那么該語言就有可能被采用。但這與你是否喜歡Scala或其他所謂更好的語言沒有半點的關系。
那么,閉包是否會使Java變得異常復雜?閉包是否真的如下面所說的那樣:
“……使編程語言變得復雜,超過了正常的可用范圍:主流程序員將放棄Java,人們還會繼續爭論下去,然后會轉向使用其它更為簡單的編程語言……
Java會變成一個只有小范圍內的專家才會使用的稀有語言”?
那請你告訴我,對JVM而言什么語言是最簡單的!我找不出最簡單的語言(非腳本語言)。專家語言(Guru language)?你在開玩笑嗎?閉包的目的是讓Java變得更簡單,因此每個人都應該開心才對。為什么要害怕呢?
一個可能的答案(非PC)是:認為“太復雜”的一方對Java的現狀很滿意,他們不關注語言優雅性的是否缺失,而且不想學習新的機制。或許這是因為在某種程度上,Java已經存在著太多的機制(或范型(paradigms))了?而對我來說,作為一個使用C++的開發人員,我已習慣了“多范型設計”,因為C++在這方面確實包羅萬象。我不會去考慮那些我目前使用不到的機制。
也許,這對Java領域來說并不簡單,因為多年以來Java程序員一直被灌輸一種常識,即世界上只有面向對象這一種范型。這可以解釋人們為什么認為泛型(generics)的加入會使語言的復雜性大大增加(另一個解釋是設計選擇上(特別對于通配符)需要考慮向后兼容性)。
因此,事情似乎是這樣:要么是由于人們對泛型機制的憤怒,要么是由于BGGA提案中包含非局部變量返回這一不常用的做法,激起了人們的強烈反對。你難道不這樣認為嗎?
3.討論
讓我們的描述得更加技術一點:為什么我們在Java里面需要閉包呢?答案很可能是:因為Ruby語言里面有這個東西。Java 社區似乎還集體性的停留在一個過去Ruby所帶來的創傷,而仿效Ruby的特性在其本身看來理由十分充分。正如過去人們在鐵幕時期常常說的那樣:學習Ruby就是學習如何取勝!所以現在,就像在Ruby(或者Groovy和C#)里一樣,我們能夠用Java這樣編程:
- Utils.forEach(names, { Integer x => System.out.println(x); }); // BGGA
而這是我們在帶有STL和lambda庫的C++里面常常使用的東西。
不過,以上的敘述也不是爭論的全部。上述三個提案中每個都有第二部分,能夠允許包含某些細致的資源管理,可以認為這是一種“現代的析構函數”。我只能說“最終這樣做了”,因為Java后期的資源管理非常麻煩。
BGGA試圖通過上面提到的非本地控制語句來達到這個目標,然后可以允許執行“execute around”模式,即所謂的“控制抽象化”,但是有些不是很直觀。在BGGA提案里,關鍵詞是根據詞匯綁定的,而且會自動指向定義了閉包的嵌套類型(enclosing type)實例。對我來說,Groovy一定程度上比較清楚的解決了非本地綁定的問題——引進了兩個閉包成員:owner(關于嵌套對象)和delegate(定義詞匯綁定)。通過這種方式,總是能夠清楚的說明我們需要什么類型的詞匯綁定,delegate的默認值是owner。
其他的提案也試圖解決同樣的問題:CICE包含了ARM(自動資源管理模塊 —— 一個特殊的模塊類型,當不使用它的時候會自動應用處理方法),而FCM 包含了JCA(Java控制抽象化 —— 這個很像上面提到的“execute around“那種類型,而且和BGGA一樣需要非本地的詞匯綁定)。
在這一點上,讓我們試著得出的一些結論是:
第一:C#的3.0版本里面有lambda,C++0x里面同時包含有lambda和auto specifier,如果Java 7里再不包含閉包的話,就會顯得十分陳舊了。
但也不要絕望,目前有一個庫解決方案(http://code.google.com/p/lambdaj/)。在這種情況下,一個庫解決方案是否足夠呢?我不知道,因為它的語法看起來不是很直觀,但是它可能已經是你能夠得到的最佳方案了。
第二:我發現此處最有教育意義的內容是:我們能夠看到析構函數的老概念其實一點也不過時!既然C#有它的使用聲明,那么Java也理應(而且需要)包含一些類似于析構函數的機制。Java這一(前)“現代語言”也需要如此。
原文:Java's Closures Debate for C++ Eyes 作者:Marek Krj