New Groovy --- Closure/Block問題 - 中國WEB開發者網絡 (http://www.webasp.net) -- 技術教程 (http://www.webasp.net/article/) --- New Groovy --- Closure/Block問題 (http://www.webasp.net/article/17/16534.htm) |
| -- 作者:未知 -- 發佈日期: 2005-02-04 |
| 在經歷了從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 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. 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 = 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的關係,我尚無力作出論斷。先走著瞧吧…… |
| webasp.net |