在經曆了從blog到mailing list的腥風血雨之後,New Groovy的 Roadmap粉墨登場。一定程度上這是對最近一段時間塵嚣甚上的 Groovy is dead的回應。另一方面,Groovy的苦谏者Mike Spille對于新Groovy特性的批評所得到的回應卻是 以wiki形式确定下來的文檔。
所有新特性中争議最大的,當然就是此條:return/break/continue to behave inside closures like these statements work in other blocks (such as the block on a for() or while() loop.
簡而言之,這就是統一block和closure。
這裡我忍不住王婆賣瓜一下,其實我已經直覺出這個味道。我在1月16日在groovy-dev上re了Mike Spille一篇:
I think we should regardas syntax-sugar, this feature eliminate the difference between built-in flow structure and user-defined method call.
foo() {}
I think there is no essential difference between closure and block. User have a block, then want to reuse the block. make the block can be passed or returned or parameterized --- that is closure.If = { condition, doSth | if (condition) doSth() } t = { println 'Hello!' } if (true) { println 'Hello!' } // built-in if If (true) { println 'Hello!' } // user-defined If is similar to built-in if, that's groovy! If(true, t) // normal method call If(true, { println 'Hello!' }) // normal method call even the last param is a anonymous closure > > The puzzling errors bit is also why I favor a keyword for closures. It'll > not only greatly simplify the grammar and parser, but I think it's better > for users too. It clearly signals where closures are in your code, and > avoids the problems of _users_ not knowing when they have a closure and > when they have a block.
現在看來,當時我已經接近擺脫最初僅僅視closure為javascript中anonymous function等價物的想法了。我已經直覺到此closure與一般block之間的微妙關系。
closure的syntax很大程度上消除了内建流程結構和使用者定義方法的差別。換言之,我們可以很友善的寫出接受closure參數的方法,使得其調用文法能非常類似while, when(不帶else的if)的文法結構!雖然還是無法複制for(;;)和if-else這樣特殊的文法構造,但這已經是很大的震撼。
進而,我發現,就應用程式員角度而言,block和closure并不存在本質差別。block可以看作一個代碼段,而closure是可以重用(可傳遞、傳回以及參數化)的代碼段。
然而closure和傳統flow控制具有一定的沖突,這主要表現在如何實作break、continue語義。之前的建議一般是要求closure傳回一個特定常量标記如Closure.BREAK來訓示,然後由closure的調用者負有責任來處理此标志。
更大的問題在于,同樣作為代碼功能抽象和複用的機關,closure與function(method)發生了沖突。這就是要命的return問題!
顯然,按照一般思路,closure在底層就是一個function或者說method,匿名closure形同block的 { param | expression } 的簡潔構造可被視作一種文法sugar。并且從最初一直到現在,此sugar主要是為了達到精巧的GPath的簡潔性。且另一方面,在從js或者是對泛函式程式設計(fp)有點入門知識的人看來,closure都應該與function劃上等号。
應該說,groovy的closure一開始也是如此。唯一是,同樣為了GPath的簡潔性,采用了可省略return的設計(直接傳回最後一個語句的值)。
從此,groovy的codebase中,所有closure幾乎都沒有return關鍵字的出現。因為幾乎沒有必要,是以似乎沒有理由記得它甚至提到它。由此,一扇門打開了……
考慮一個傳統程式。這個函數檢查名為file的檔案的每一行,并傳回滿足filter的第一行行号,否則傳回-1。
def findLine(file, filter) {
lineNo = 0
f = new File(file)
while(!f.isEOF()) {
line = f.readLine()
if (filter(line)) return lineNo;
else ++lineNo
}
return -1
}
現在一個newbie剛剛看了幾個closure的例子,興奮異常,于是換用closure style來撰寫:
def findLine(file, filter) {
lineNo = 0
new File(file).eachLine { line |
if (filter(line)) return lineNo; else ++lineNo
}
return -1
}
Ok,一切看上去沒錯,代碼更清晰的表達了程式的主旨,幾乎沒有多餘的東西。
但是,不幸的是,這段程式在Classic Groovy裡面是無法達到預期效果的,findLine總是傳回-1!
因為在eachLine之後的匿名closure中的return是從該closure傳回到eachLine方法内部的調用點上(事實上eachLine方法隻是簡單的忽略這個傳回值,沒有任何人期待它的到來),而不是如程式員所期望的傳回給findLine的調用者!
ok,這是程式員的問題,誤用了return——有人會簡單的下結論。如何用建議的Closure.BREAK常量标志來達成這個功能先不論,僅僅考慮到這個return是多麼直覺的事情,并且groovy的目标就是要讓程式員能按直覺辦事(去掉煩心的程式終結符;居然還允許跨行,就是例證),就不能簡單的把問題歸咎于程式員。
照例說,在這個結構裡面,return的用意非常清楚,不帶偏見的說,任何一個java程式員轉向groovy之後幾乎都會寫出這樣的程式。由于 closure大量被用于簡化疊代結構(Martin Flower号稱他是以在沒有closure的語言中是如此懷念closure),對于從c-style轉過來的程式員來說,面對最常使用的each,自然把它看作for, while結構的替代物,是以把return視作從findLine傳回,而不是從匿名closure傳回是狠自然的。
對此問題,Mike Spille的意見是:一切起因于closure實在太像block,程式員忘記了此處實際上是個文法sugar,本質是個method而并不是一個 block,因而誘導了程式員錯誤的期望該return的傳回位置。既然如此,我們就應該明确哪裡是block哪裡是closure。其建議就是增加一個訓示closure的關鍵字,譬如def { closure code... }
對此,有人尖銳的指出,那還不如去用C#的delegate!(另一方面,那也是js的實際情況,function關鍵字就相當于此關鍵字)
無論如何,情況很清楚:普通block和closure在syntax上如此相似以至于無法分辨,解決方案無外乎,區分它們(Mike Spille的建議),或者統一它們——使得break/return等的語義在block和closure中一緻!
John Rose提出的并且為多數人接受的方案是,應該令closure的return如程式員最可能期望的那樣行為:傳回到定義其的函數上。并且他還參照 smalltalk, ruby, lisp等提出了break, continue在closure中的一攬子改進文法和語義。由此,通過在closure中使用continue L:value的文法結構,可以達到從label L處傳回value的需求。(當然實際需要使用這樣怪異的continue的機會其實很少,按照我一貫的語調——你當然可以用xxx,但是如果需要用到 xxx,那往往是壞味道的信号……)
對此提案,Mike Spille自然是全力反對,對此的指責還牽涉上了開發進度、管理模式等問題。如我沒有看錯,Mike Spille的rant(雖然他自己不承認)甚至多次要求幾個核心人物下台……:D
順便說一下,在我寫前面那個mail的時候,還沒有意識到這個問題。John Wilson的re中向我指出了這個棘手問題:
On 16 Jan 2005, at 13:53, Shijun He wrote:
> I think there is no essential difference between closure and block.
> User have a block, then want to reuse the block. make the block can be
> passed or returned or parameterized --- that is closure.
>
this is almost true but not quite..
if (a == 1) {
return // returns from enclosing function
}
a.each {
return // returns from closure
}
this (and break in a closure) catches people out a lot.
This has been discussed a great deal but we have not come up with a
solution which pleases everybody.
顯然,要pleases everybody是mission impossible :( 事實上吵的天翻地覆……Mike Spille已經被冠以FUD的名号了。
最近幾天,我也跟pt同志讨論了這個問題,并且還拉出了ruby和lisp/scheme來“尋根”。pt同志的意見與Mike Spille出奇的一緻。pt堅持認為closure就應該是function,改變return的語義是不可接受的。但是ruby的現狀卻是更接近 John Rose所說的(至少匿名closure的return不傳回closure本身而傳回到上層函數)。如果我們不懷疑John Rose的專業水準(這點倒是連Mike Spille也承認的,John是個有多種語言經驗的高手),那麼closure的這種return語義,是Smalltalk的語言标準規定的,而 ruby有大量的概念是師從smalltalk的,closure應該也不例外。進一步的,連Groovy的主要設計者James Strachan都發話了:我們不是要改變什麼,而是要把從來沒有明确下來的return/break/continue的語義明确下來,是要把尚未完全實作的closure功能完全化。(我亂說一句:這個有馬後炮之嫌:))。
問題最後進入了“哲學”階段(請千萬不要誤會我是在調侃哲學)。那就是“什麼是closure”。顯然,目前為止我更相信John Rose的說法,lisp以來的closure都是如此,即一種特殊的block結構體。連Mike Spille的許多議論也隻是說,groovy的目标是java程式員,應此不必全盤“西化”,應首先考慮java程式員的接受程度,譬如 closure/block的統一會破壞對java程式結構的認識根基。(不過此點在我看了'Java中的closure'這篇奇聞逸事之後,就根本不能說服我了。)
common lisp,具有特殊的操作符就叫做block,還有return-from,可以return到任何指定的層級!可謂是超級徹底的block!!另一方面,pt強烈認為closure就是lambda,或者說是lambda在指令式語言(OO語言?)中的複制品。他還認為跳轉到上級lex應該以 call/cc方式實作。俺對lisp/scheme所知甚少,也不可能想清楚這當子事情。不過至少有一點是清楚的:Groovy的選擇已經作出,并且我也覺得這是符合其核心價值的。至于其與block, func, method的關系,我尚無力作出論斷。先走着瞧吧……