for ( Iterator i = c.iterator(); i.hasNext(); ){String s = (String) i.next(); ... }
現(xiàn)在,多虧了這種新的優(yōu)雅的迭代語(yǔ)法,以及泛型的引入,我們可以用下面的代碼:
for ( String s : c){ ... }
很明顯,可以少輸入很多字符。但請(qǐng)考慮一個(gè)問(wèn)題:為什么花了10年才引入了這個(gè)特性?不過(guò)我們先不考慮這個(gè)重要的問(wèn)題,再看看另一種語(yǔ)言,Python。
Python在很多方面都是一個(gè)更加適合編程的語(yǔ)言。它的衍化要比Java快得多。例如,Python 2.2引入了生成器(generator)。一個(gè)生成器是一個(gè)可以產(chǎn)生多個(gè)值得函數(shù),在每次調(diào)用時(shí)都會(huì)保存狀態(tài)。下面是一個(gè)簡(jiǎn)單的例子:
def counter(n): whileTrue: yield n n = n + 1
因?yàn)檫@個(gè)函數(shù)定義包含了關(guān)鍵詞yield
,所以Python就可以知道它是一個(gè)生成器。可以像下面這樣使用counter
生成器:
c12 = counter(12) c12.next() c12.next()
第一行創(chuàng)建了從12開始計(jì)數(shù)的生成器的實(shí)例。第二行告訴生成器運(yùn)行到產(chǎn)生(yield)一個(gè)值為止。第三行告訴生成器繼續(xù)運(yùn)行直到產(chǎn)生了另一個(gè)值。該生成器所產(chǎn)生的前兩個(gè)值分別是整數(shù)12和13。
這確實(shí)是一個(gè)很酷的特性:它讓程序員能寫出更簡(jiǎn)單的代碼,而不會(huì)使生成器變得復(fù)雜和容易出錯(cuò)。為何Java不能學(xué)習(xí)Python呢?
我們也先將第二個(gè)問(wèn)題放一邊,思考一下如何用另一種語(yǔ)言來(lái)實(shí)現(xiàn)Python的生成器,這種語(yǔ)言就是Scheme——世界上有一些自以為是的怪人就用它。Scheme是Lisp的一種方言,它從誕生到現(xiàn)在已經(jīng)存在了大約30年了。Lisp則已經(jīng)存在了大約50年了。
下面是我可以完成的對(duì)Python中counter
生成器模仿最好的Scheme實(shí)現(xiàn):
對(duì)資深Lisp程序員多說(shuō)一句:下面我要演示的子程序要比官方的累加器例子復(fù)雜得多,這是因?yàn)槲沂前凑樟?a >Python生成器的語(yǔ)義來(lái)寫的。
( define (counter n)(letrec((generator (lambda(yield)(let counter ((n n))(call-with-current-continuation(lambda(continue)(set! generator (lambda(k)(set! yield k)(continue n)))(yield n)))(counter (+ n 1))))))(lambda()(call-with-current-continuation(lambda(yield)(generator yield))))))
“我靠!”你可能會(huì)有這種反應(yīng)。確實(shí)太復(fù)雜了!在寫這段代碼的最初版本的時(shí)候,我說(shuō)寫這個(gè)不會(huì)太難。然后我發(fā)現(xiàn)了一個(gè)可能導(dǎo)致死循環(huán)的錯(cuò)誤,而引發(fā)錯(cuò)誤不是小概率事件。所以最后我認(rèn)同了這點(diǎn):如果只是要寫一個(gè)能和Python生成器效果一樣的函數(shù),還是不要寫這樣的子過(guò)程的比較好。不過(guò),這個(gè)可怕的東西在客戶端代碼使用起來(lái)卻十分簡(jiǎn)單:
( define c12 (counter 12))(c12)(c12)
第一行定義了c12是子過(guò)程counter
給一個(gè)參數(shù)12調(diào)用時(shí)的結(jié)果。第二行和第三行直接調(diào)用c12,沒(méi)有任何參數(shù),就和Python的例子一樣,返回了12和13。不過(guò)這些都是學(xué)院派的,沒(méi)有哪個(gè)瘋子會(huì)在普通需求下寫一個(gè)這樣的子過(guò)程。
寫像counter
這樣的子過(guò)程一般會(huì)導(dǎo)致手指抽痙、頭腦發(fā)脹。不過(guò),有意思的是,我們可以跳過(guò)這些來(lái)寫counter
,Scheme的生成器要比Python版本的更加容易使用,因?yàn)镾cheme的返回的是函數(shù),而Python生成器返回的是生成器對(duì)象,所以Python生成器需要調(diào)用next
方法。
(旁白:Python生成器的設(shè)計(jì)師們本可以這樣實(shí)現(xiàn)生成器對(duì)象:接下來(lái)的值通過(guò)c12()
或c12.next()
來(lái)獲取,不過(guò)他們并沒(méi)有這樣實(shí)現(xiàn)。)
回到Scheme上……在Scheme中寫這樣的生成器的復(fù)雜和容易出錯(cuò)看上去似乎讓在Scheme中使用生成器變得不切實(shí)際,但實(shí)際上并非如此,因?yàn)镾cheme包含了一個(gè)Python和Java都缺乏的特性:擴(kuò)展語(yǔ)言語(yǔ)法的能力。如果你能夠?qū)懗鯯cheme版本的counter
,花不了多少功夫就可以創(chuàng)建一個(gè)宏(macro)使得這個(gè)特性能以一種可以被大家接受的方式使用。下面是我寫的宏,可以完成這個(gè)任務(wù):
( define-syntax define-generator (syntax-rules()((define-generator (NAME ARG ...) YIELD-PROC E1 E2 ...)(define(NAME ARG ...)(letrec((generator (lambda(yield)(let((YIELD-PROC (lambda v (call-with-current-continuation(lambda(continue)(set! generator (lambda(k)(set! yield k)(apply continue v)))(apply yield v))))))(let NAME ((ARG ARG) ...) E1 E2 ...)))))(lambda()(call-with-current-continuation(lambda(yield)(generator yield)))))))))
一旦有了這個(gè)宏,counter
生成器的Scheme版本就可以這樣定義了:
(define-generator (counter n) yield (counter (+ 1(yield n))))
還不錯(cuò)吧?這個(gè)版本唯一讓我煩的地方是必須指定yield
函數(shù)的名稱。不過(guò)它還是給予程序員一些靈活性,可以根據(jù)代碼的上下文來(lái)給函數(shù)起一個(gè)最有意義的名稱。(其實(shí),資深的Lisp程序員應(yīng)該知道這個(gè)“特性”可以使用一些非hygenic宏來(lái)修正,不過(guò)這里我們還是堅(jiān)持標(biāo)準(zhǔn)R5RS Scheme)。
如果你比較一下第一版和第二版的counter
,你可能會(huì)注意到我在新的define-generator
版本中作了一些小手腳:yield
函數(shù)返回了它產(chǎn)生的值,因此它可以用于對(duì)counter
的遞歸調(diào)用中。而Python的生成器就不能這樣用。
那么為什么Java不能變得更像Python?答案是——其實(shí)Java和Python很像:Python的用戶也等了將近10年才可以用上生成器。而我在玩了幾天之后花了幾個(gè)小時(shí)就在Scheme中加入了對(duì)生成器的支持。不過(guò)還是有人會(huì)說(shuō)生成器以及最近的其他一些Python特性,如列表包容,都使得Python變得更加容易編寫——我當(dāng)然完全同意這個(gè)觀點(diǎn)——但是,從根本上來(lái)說(shuō),Java和Python在這一點(diǎn)上是一樣的——都不能修改語(yǔ)言本身。
Java、Python和幾乎所有其他非Lisp語(yǔ)言都是讓你任由語(yǔ)言設(shè)計(jì)者擺布。你要等他們實(shí)現(xiàn)你需要的語(yǔ)言特性,可能只有列表中的前幾個(gè)。而且等他們弄個(gè)一個(gè)新東西給你搔搔癢,誰(shuí)能確定你喜歡這種結(jié)果?
那么為何花了10年才在Java中有了增強(qiáng)迭代語(yǔ)法?這是因?yàn)樵贘ava和很多其他編程語(yǔ)言一樣,語(yǔ)法是一個(gè)大問(wèn)題。一般用戶都不會(huì)修改語(yǔ)言的語(yǔ)言。因?yàn)檫@很難完成,只有少數(shù)人學(xué)習(xí)了這種技術(shù)才能去做。當(dāng)需要修改語(yǔ)法時(shí),表達(dá)力和清晰的優(yōu)先級(jí)都沒(méi)有保持向后兼容重要。
在Scheme中,加入語(yǔ)法相對(duì)還比較簡(jiǎn)單,同時(shí)還可以根據(jù)特定問(wèn)題的基礎(chǔ)來(lái)完成,所以無(wú)須擔(dān)心是有要給出一個(gè)普遍適用的理想解決方案。這種能根據(jù)問(wèn)題構(gòu)建語(yǔ)言的能力要?jiǎng)龠^(guò)對(duì)使用很多括號(hào)的語(yǔ)言的擔(dān)心。