在經曆了從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 regard foo() {} as syntax-sugar, this feature eliminate the difference between built-in flow structure and user-defined method call.
If = { condition, doSth | if (condition) doSth() }t = { println 'Hello!' }if (true) { println 'Hello!' } // built-in ifIf (true) { println 'Hello!' } // user-defined If is similar tobuilt-in if, that's groovy!If(true, t) // normal method callIf(true, { println 'Hello!' }) // normal method call even the lastparam 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.
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.
現在看來,當時我已經接近擺脫最初僅僅視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 = 0f = 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 asolution 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的關係,我尚無力作出論斷。先走著瞧吧……