冒號(hào)和他的學(xué)生們
——程序員提高班紀(jì)事
- 數(shù)據(jù)類型
遵禮謂之劬,守法謂之固,此荒國(guó)之風(fēng)也 ——《荀悅·申鑒》
待教室平靜下來(lái),冒號(hào)再度開(kāi)腔:“在談?wù)搫?dòng)態(tài)語(yǔ)言之前,最好先澄清一下它與動(dòng)態(tài)類型語(yǔ)言之間的區(qū)別。”
嘆號(hào)訝然道:“它們不是一回事嗎?一直以為動(dòng)態(tài)語(yǔ)言是動(dòng)態(tài)類型語(yǔ)言的簡(jiǎn)稱呢。”
“有親戚之名,卻無(wú)血緣之親。名稱上相似,加之動(dòng)態(tài)語(yǔ)言絕大多數(shù)確是動(dòng)態(tài)類型語(yǔ)言,造成混淆實(shí)屬在所難免,但二者之間并無(wú)必然聯(lián)系——?jiǎng)討B(tài)語(yǔ)言不一定是動(dòng)態(tài)類型語(yǔ)言,動(dòng)態(tài)類型語(yǔ)言也不一定是動(dòng)態(tài)語(yǔ)言。”冒號(hào)飛跑的舌頭幾乎絆蒜,同時(shí)把眾人的腦子攪成了一鍋粥。
見(jiàn)勢(shì)不妙,冒號(hào)改用迂回戰(zhàn)術(shù):“我們不妨再談開(kāi)些,大家對(duì)數(shù)據(jù)類型是如何理解的?”
逗號(hào)隨口道:“數(shù)據(jù)類型不就是數(shù)據(jù)的種類嗎?”
眾人暗笑:說(shuō)了跟沒(méi)說(shuō)差不多。
冒號(hào)說(shuō)道:“數(shù)據(jù)類型包含兩個(gè)要素:一個(gè)是允許取值的集合,一個(gè)是允許參與的運(yùn)算。例如int類型在Java中既定義了介于− 231 和231 − 1之間的整數(shù)集合,也定義了該集合上的整數(shù)所能進(jìn)行的運(yùn)算。現(xiàn)在的問(wèn)題是:數(shù)據(jù)類型的意義何在?”
句號(hào)回答:“限定一個(gè)變量的數(shù)據(jù)類型,就意味著限制了該變量的取值范圍和所參與的運(yùn)算,這從一定程度上保證了代碼的安全性。”
冒號(hào)追問(wèn):“還有嗎?”
句號(hào)略作思考后說(shuō):“用戶自定義的數(shù)據(jù)類型,如C中的結(jié)構(gòu)和Java中的類或接口,賦予數(shù)據(jù)以邏輯內(nèi)涵,提高了代碼的抽象性。”
“精辟!”冒號(hào)贊道,“數(shù)據(jù)類型既有針對(duì)機(jī)器的物理意義,又有針對(duì)人的邏輯意義。前者用于進(jìn)行底層的內(nèi)存分配和數(shù)值運(yùn)算等,后者用于表達(dá)高層的邏輯概念。既然類型如此重要,類型檢查就必不可少了。所謂動(dòng)態(tài)類型語(yǔ)言(Dynamic Typing Language),正是指類型檢查發(fā)生在運(yùn)行期間(run-time)的語(yǔ)言。”
“那靜態(tài)類型語(yǔ)言(Static
Typing Language)自然是類型檢查發(fā)生在編譯期間(compile-time)的語(yǔ)言咯。”引號(hào)接話道。
冒號(hào)回應(yīng):“一般的說(shuō)法是這樣,不過(guò)我更愿意將‘編譯期間’四個(gè)字改為‘運(yùn)行之前’,否則容易讓人誤解為靜態(tài)類型語(yǔ)言一定是編譯型語(yǔ)言(Compiled
Language)。”
問(wèn)號(hào)問(wèn)道:“是否可以這么說(shuō):靜態(tài)類型語(yǔ)言需要變量申明,而動(dòng)態(tài)類型語(yǔ)言則不需要?”
“這話只對(duì)了一半。”冒號(hào)評(píng)論,“動(dòng)態(tài)類型語(yǔ)言固然不需要顯式的變量申明(Explicit Declaration),一些靜態(tài)類型語(yǔ)言有時(shí)也不需要。典型的如ML、Haskell之類的函數(shù)式語(yǔ)言,編譯器可以通過(guò)上下文來(lái)進(jìn)行類型推斷(type
inference)。”
嘆號(hào)感慨:“動(dòng)態(tài)類型語(yǔ)言不必申明變量,甚至一個(gè)變量在不同地方可以代表不同類型,多省事多方便啊!”
冒號(hào)微微頷首:“動(dòng)態(tài)類型語(yǔ)言的確有簡(jiǎn)明快捷的優(yōu)勢(shì),并且天然具有泛型(generic)特征,代碼更加靈活。比如,動(dòng)態(tài)類型有一種被稱作鴨子類型(Duck
Typing)的形式。”
逗號(hào)感到有趣:“鴨子類型?很滑稽的名字。”
“這種類型的思想是:如果一個(gè)對(duì)象既會(huì)走鴨步又會(huì)呷呷叫,何妨將其視作鴨子呢?”冒號(hào)說(shuō)著投影出一段Ruby代碼——
class Duck #會(huì)叫會(huì)游的鴨
def shout
puts '呷呷呷'
end
def swim
puts '鴨泳'
end
end
class Frog #會(huì)叫會(huì)游的蛙
def shout
puts '呱呱呱'
end
def swim
puts '蛙泳'
end
end
def shoutAndSwim(duck) #讓一只會(huì)叫會(huì)游的家伙邊叫邊游
duck.shout
duck.swim
end
shoutAndSwim(Duck.new) #讓一只鴨邊叫邊游
shoutAndSwim(Frog.new) #讓一只蛙邊叫邊游
冒號(hào)繼續(xù)講解:“在Smalltalk、Python和Ruby等動(dòng)態(tài)類型的OO語(yǔ)言中,只要一個(gè)類型具有shout和swim的方法,它就可以為shoutAndSwim所接受。在Java這種靜態(tài)類型語(yǔ)言中是不可能的,除非鴨和蛙在同一繼承樹(shù)上,或者二者均顯式實(shí)現(xiàn)了一個(gè)包含shout和swim的公用接口。”
句號(hào)敏銳地指出:“C++是靜態(tài)類型語(yǔ)言,但它的模板也可實(shí)現(xiàn)類似功能,并不需要引入繼承關(guān)系。”
“非常正確!但請(qǐng)接著看下去。”冒號(hào)又放出一段投影——
class Cock #會(huì)叫不會(huì)游的雞
def shout
puts '喔喔喔'
end
end
class Fish #會(huì)游不會(huì)叫的魚
def swim
puts '自由泳'
end
end
def shoutOrSwim(duck, flag) #讓一只會(huì)叫或會(huì)游的家伙叫或游
flag ? duck.shout : duck.swim
end
shoutOrSwim(Cock.new, true) #讓一只雞叫
shoutOrSwim(Fish.new, false) #讓一只魚游
“這里雞沒(méi)有swim的方法,魚沒(méi)有shout的方法。若采用C++的模板,shoutOrSwim是無(wú)法通過(guò)編譯的。但在支持Duck
類型的語(yǔ)言中,只要在運(yùn)行期間不讓雞swim、讓魚shout——除非你突發(fā)奇想——一切平安無(wú)事。”冒號(hào)作了個(gè)OK的手勢(shì)。
“動(dòng)態(tài)類型語(yǔ)言真是越看越可愛(ài)。”嘆號(hào)簡(jiǎn)直垂涎欲滴了。
“Duck類型為軟件重用開(kāi)啟了新的窗口,但凡事都是一分為二的。由于Duck類型的接口組合是隱性的,其使用者需要比普通Interface更小心以避免誤用;其維護(hù)者也需要更小心以避免破壞客戶代碼;此外它也可能造成濫用——將會(huì)叫會(huì)游的東西放進(jìn)池塘似乎不算壞主意,但如果一艘輪船趁機(jī)也開(kāi)了進(jìn)來(lái),恐怕就不那么美妙了。”
眾皆莞爾。
“再來(lái)看看靜態(tài)類型語(yǔ)言的好處:由于在運(yùn)行之前進(jìn)行了類型檢查,一方面代碼的可靠性增強(qiáng),另一方面編譯器有可能藉此優(yōu)化機(jī)器代碼以提高運(yùn)行效率,同時(shí)相比前者節(jié)省了運(yùn)行期的類型檢查時(shí)間。此外,變量類型的聲明表明了編程者的意圖,有輔助文檔的功效。”冒號(hào)解釋著,“兩種類型的體制可以用兩種法律原則來(lái)類比:靜態(tài)類型檢查類似‘疑罪從有’的有罪推定制——在被證明合法之前是非法的,動(dòng)態(tài)類型檢查類似‘疑罪從無(wú)’的無(wú)罪推定制——在被證明非法之前是合法的。至于如何取舍,套用一句話:‘Static Typing
Where Possible, Dynamic Typing When Needed’。”
問(wèn)號(hào)提出新問(wèn)題:“動(dòng)態(tài)類型語(yǔ)言與弱類型語(yǔ)言有何不同?”
冒號(hào)喟言:“它們也常常被混為一談,但類型的動(dòng)靜與強(qiáng)弱完全是正交的兩個(gè)概念。前者以類型的綁定(binding)時(shí)間來(lái)劃分,后者以類型的剛性強(qiáng)度來(lái)劃分。通常弱類型語(yǔ)言(Weakly-typed Language)允許一種類型的值隱性轉(zhuǎn)化為另一種類型,而強(qiáng)類型語(yǔ)言(Strongly-typed Language)則不允許。舉個(gè)例子,1+"2"在VB中等于3——第二個(gè)字符串轉(zhuǎn)化為整數(shù);在Javascript中等于"12"——第一個(gè)整數(shù)轉(zhuǎn)化為字符串;在C中則等于一個(gè)不定的整數(shù)值——第二個(gè)字符串作為地址來(lái)運(yùn)算。這樣似乎很有趣很方便,但程序容易藏污納垢,滋生臭蟲(chóng)(bug)。”
引號(hào)想起:“好像還有一種所謂的類型安全語(yǔ)言?”
逗號(hào)緊緊抱著頭,仿佛害怕裂開(kāi)。
“類型按安全性來(lái)劃分,可分為類型安全語(yǔ)言(Type-safe
Language)和類型不安全語(yǔ)言(Type-unsafe
Language)。一般認(rèn)為強(qiáng)類型語(yǔ)言是類型安全的,弱類型語(yǔ)言是類型不安全的。”冒號(hào)應(yīng)道,“至此,我們已論及數(shù)據(jù)類型的三種劃分方式。值得一提的是,這些劃分并非涇渭分明的,更多的是定性而非定量的描述,甚至沒(méi)有公認(rèn)統(tǒng)一的定義。但了解它們,對(duì)我們?nèi)蘸蟮膶W(xué)習(xí)是有所裨益的。”