第21期:常規(guī)遍歷語(yǔ)法

遍歷可以說(shuō)是最基本的集合運(yùn)算了,比如求和、計(jì)數(shù)、尋找***最小值等聚合運(yùn)算,按條件過(guò)濾集合、根據(jù)集合成員生成另一個(gè)新集合,也都是遍歷運(yùn)算。集合化語(yǔ)法要求我們能用很短的語(yǔ)句(經(jīng)常就只有一句,而不是若干語(yǔ)句構(gòu)成的一段程序)來(lái)描述大部分遍歷運(yùn)算,這樣我們需要考查遍歷運(yùn)算中可能出現(xiàn)的各種常見情況,并設(shè)計(jì)出合理自洽的語(yǔ)法規(guī)則。
我們從簡(jiǎn)單到復(fù)雜來(lái)考查遍歷運(yùn)算中的可能情況,并討論SQL語(yǔ)法在這方面的表現(xiàn)。
1. 直接針對(duì)集合成員運(yùn)算
比如計(jì)算集合成員的合計(jì)。
這是最簡(jiǎn)單的情況,采用普通的函數(shù)語(yǔ)法風(fēng)格就可以,將待遍歷的集合作為參數(shù)獲得返回值,比如sum(A)用于計(jì)算集成A成員的合計(jì),當(dāng)然也可以使用對(duì)象式的語(yǔ)法風(fēng)格寫成A.sum()。
2. 引用集合成員
比如我們不是要計(jì)算集合成員的合計(jì),而是要計(jì)算平方和,那么這個(gè)平方該如何描述?
這就會(huì)用到我們?cè)谡劶匣Z(yǔ)法時(shí)提到的lambda語(yǔ)法。平方這個(gè)運(yùn)算本質(zhì)上是一個(gè)函數(shù),在遍歷過(guò)程中它以被遍歷集合的當(dāng)前成員作為參數(shù),返回該參數(shù)的平方。而lambda語(yǔ)法允許將這個(gè)函數(shù)以表達(dá)式的形式并一起寫入整個(gè)計(jì)算遍歷運(yùn)算式,一個(gè)語(yǔ)句就可以完成。但這里就有一個(gè)問題,我們?cè)谶@個(gè)lambda表達(dá)式中用什么標(biāo)識(shí)符或符號(hào)表示這個(gè)當(dāng)前成員呢?
顯然,象普通函數(shù)那個(gè)先定義參數(shù)名不是個(gè)好辦法,那會(huì)讓lamdba表達(dá)式寫得很臃腫,失去lambda語(yǔ)法的簡(jiǎn)潔性。盡管有些程序設(shè)計(jì)語(yǔ)言確實(shí)是這么做的,不過(guò)我們并不提倡。使用一個(gè)固定的標(biāo)識(shí)符也不好,太長(zhǎng)了用起來(lái)不方便,太短又很可能與其它局部變量重名導(dǎo)致歧義。我們提倡在這里使用一個(gè)特殊符號(hào)來(lái)完成這個(gè)目的。
比如使用~表示當(dāng)前成員時(shí),平方和就可以寫成A.sum(~*~),簡(jiǎn)單易懂。也可以分兩步做,先計(jì)算出集合成員的平方構(gòu)成一個(gè)新集合,再計(jì)算新集合的合計(jì),寫成類似A.(~*~).sum()的形式,后一步不再需要~寫法,前一步仍需要~寫法來(lái)描述平方這個(gè)表達(dá)式函數(shù)。
3. 使用結(jié)構(gòu)化數(shù)據(jù)時(shí)引用字段
但是,我們發(fā)現(xiàn),被認(rèn)為是集合化語(yǔ)言的SQL中并沒有使用某個(gè)符號(hào)或標(biāo)識(shí)符來(lái)表示當(dāng)前遍歷成員,那么SQL又是怎么解決問題2的呢?
事實(shí)上,SQL并沒有普通意義上可由任何成員構(gòu)成的集合。SQL的集合就是表,而表的成員都是相同結(jié)構(gòu)的記錄。SQL體系中有記錄這個(gè)概念,但并不能把記錄作為一種數(shù)據(jù)類型來(lái)引用。如果我們要在SQL中針對(duì)一個(gè)單值成員的集合進(jìn)行遍歷,也只能把單值做成只有一個(gè)字段的記錄,而針對(duì)這些記錄構(gòu)成的表進(jìn)行遍歷。所有計(jì)算都是針對(duì)某些字段進(jìn)行的,而不能針對(duì)整條記錄。
但這和SQL沒有表示當(dāng)前成員的符號(hào)有什么關(guān)系呢?
我們?cè)谇懊嬲f(shuō)集合化語(yǔ)法時(shí)還提到,面向結(jié)構(gòu)化數(shù)據(jù)計(jì)算的集合化語(yǔ)法需要有簡(jiǎn)潔的方式引用字段,SQL提供了可以直接引用字段的便捷機(jī)制,而SQL又只能計(jì)算字段,那就可以不必再提供引用當(dāng)前成員(記錄)的手段了。比如SQL中計(jì)算平方和一定是某個(gè)字段的平方和,而整條記錄(集合成員)的平方則沒有意義。
SQL犧牲了集合的表達(dá)能力而簡(jiǎn)化了語(yǔ)法。對(duì)于能夠支持泛型成員構(gòu)成集合的語(yǔ)言來(lái)講,~寫法就是必要的了。而且,如果用于結(jié)構(gòu)化數(shù)據(jù)計(jì)算時(shí),SQL這種可以直接字段的寫法也要得到支持才會(huì)方便,計(jì)算某銷售帳目的金額時(shí)寫成"~.單價(jià)*~.數(shù)量"顯然不如寫成"單價(jià)*數(shù)量“更為簡(jiǎn)單直觀,好的程序語(yǔ)言應(yīng)當(dāng)借鑒SQL這種風(fēng)格。
4. 嵌套引用時(shí)的規(guī)則
遍歷在本質(zhì)上就是一個(gè)循環(huán),而循環(huán)語(yǔ)句可能有多層,這樣遍歷也可能會(huì)有嵌套引用。比如計(jì)算A,B兩個(gè)集合的交集,簡(jiǎn)單的算法就是遍歷A的成員,看是不是在B集合中出現(xiàn)過(guò)(也是遍歷),這就會(huì)涉及到兩層的遍歷。
這時(shí)候~寫法就會(huì)產(chǎn)生歧義了,~到底是指A集合還是B集合的當(dāng)前成員,這需要在語(yǔ)法規(guī)則上做一個(gè)明確的約定。
一般采用的是就近原則,即如果沒有指明~是哪個(gè)集合的,那缺省認(rèn)為是內(nèi)層遍歷集合的,而外層遍歷集合的當(dāng)前成員則需要顯式地指出其從屬于哪個(gè)集合。計(jì)算交集的表達(dá)式就可以寫成A.select(B.count(~==A.~)>0),其中的~缺省表示B的當(dāng)前成員,而另一個(gè)要顯式地寫成A.~以示區(qū)分。
面向結(jié)構(gòu)化數(shù)據(jù)計(jì)算時(shí)可以直接引用字段名,這時(shí)也可能產(chǎn)生內(nèi)外層的歧義,也可以適用于就近原則,SQL就是這樣。當(dāng)內(nèi)外層表有相同字段名時(shí),則缺省被認(rèn)為是內(nèi)存表的字段,引用外層表的同名字段時(shí)必須顯式地寫上表名;如果內(nèi)外存表中沒有相同字段名,則可以正確識(shí)別出來(lái)而不必書寫表名。
遍歷運(yùn)算雖然很基本,但設(shè)計(jì)其語(yǔ)法時(shí)仍有一些注意事項(xiàng)。SQL在這方面總體表現(xiàn)不錯(cuò),除了缺乏泛型成員的集合外,用于描述常規(guī)遍歷運(yùn)算還是比較方便簡(jiǎn)捷的。