冒號(hào)課堂§5.2:數(shù)據(jù)類型

           

          冒號(hào)課堂

          第五課 語言小談(2)

          5.2數(shù)據(jù)類型——規(guī)則與變通

          操縱于規(guī)矩之中,神明于規(guī)矩之外                        ——《俞震·古今醫(yī)案按》

           

          關(guān)鍵詞:      數(shù)據(jù)類型,靜態(tài)類型,動(dòng)態(tài)類型,Duck類型,強(qiáng)類型,弱類型,類型安全

          摘要:   關(guān)于數(shù)據(jù)類型的討論

           

          預(yù)覽

          ·           Duck類型的哲學(xué)是:名義不重要,重要的是能力

          ·           將一個(gè)會(huì)叫會(huì)游的家伙放進(jìn)池塘看起來不算壞主意,但如果一艘輪船趁機(jī)也轟隆隆地開了進(jìn)來,事情恐怕就不那么美妙了

          ·           靜態(tài)類型檢查類似“疑罪從有”的有罪推定制,動(dòng)態(tài)類型檢查類似“疑罪從無”的無罪推定制

          ·           盡可能守規(guī)則,必要時(shí)求變通

          ·           規(guī)則如褲帶,過于寬松和過于束縛都不好

           

           提問

          ·           動(dòng)態(tài)語言與動(dòng)態(tài)類型語言是一回事嗎?

          ·           數(shù)據(jù)類型有哪兩個(gè)要素?其意義何在?

          ·           什么是動(dòng)態(tài)類型和靜態(tài)類型?它們的區(qū)別是什么?各有什么優(yōu)缺點(diǎn)?

          ·           什么是鴨子類型(duck typing)?它有什么優(yōu)缺點(diǎn)?

          ·           什么是強(qiáng)類型與弱類型?什么是類型安全的?


          講解

          待教室平靜下來,冒號(hào)再度開腔:“在談?wù)搫?dòng)態(tài)語言之前,最好先澄清一下它與動(dòng)態(tài)類型語言之間的區(qū)別。”

          嘆號(hào)訝然道:“它們不是一回事嗎?一直以為動(dòng)態(tài)語言是動(dòng)態(tài)類型語言的簡稱呢。”

          “有親戚之名,卻無血緣之親。名稱上相似,加之動(dòng)態(tài)語言絕大多數(shù)確是動(dòng)態(tài)類型語言,造成混淆實(shí)屬在所難免,但二者之間并無必然聯(lián)系——?jiǎng)討B(tài)語言不一定是動(dòng)態(tài)類型語言[1],動(dòng)態(tài)類型語言也不一定是動(dòng)態(tài)語言[2]。”冒號(hào)飛跑的舌頭幾乎絆蒜,同時(shí)把眾人的腦子攪成了一鍋粥。

          見勢(shì)不妙,冒號(hào)改用迂回戰(zhàn)術(shù):“我們不妨再談開些,大家對(duì)數(shù)據(jù)類型是如何理解的?”

          逗號(hào)隨口道:“數(shù)據(jù)類型不就是數(shù)據(jù)的種類嗎?”

          眾人暗笑:說了跟沒說差不多。

          冒號(hào)說道:“數(shù)據(jù)類型包含兩個(gè)要素:一個(gè)是允許取值的集合,一個(gè)是允許參與的運(yùn)算。例如int類型在Java中既定義了介于− 231 231 − 1之間的整數(shù)集合,也定義了該集合上的整數(shù)所能進(jìn)行的運(yùn)算。現(xiàn)在的問題是:數(shù)據(jù)類型的意義何在?”

          句號(hào)回答:“限定一個(gè)變量的數(shù)據(jù)類型,就意味著限制了該變量的取值范圍和所參與的運(yùn)算,這從一定程度上保證了代碼的安全性。”

          冒號(hào)追問:“還有嗎?”

          句號(hào)略作思考后說:“用戶自定義的數(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á)高層的邏輯概念。既然類型如此重要,類型檢查就必不可少了[3]。所謂動(dòng)態(tài)類型語言dynamic typing language),正是指類型檢查發(fā)生在運(yùn)行期間run-time)的語言。”

          “那靜態(tài)類型語言static typing language)自然是類型檢查發(fā)生在編譯期間compile-time)的語言咯。”引號(hào)接話道。

          冒號(hào)回應(yīng):“一般的說法是這樣,但我更愿意將‘編譯期間’四個(gè)字改為‘運(yùn)行之前’,否則容易讓人誤解為靜態(tài)類型語言一定是編譯型語言(compiled language)。”

          問號(hào)問道:“是否可以這么說:靜態(tài)類型語言需要變量聲明,而動(dòng)態(tài)類型語言則不需要?”

          “這話只對(duì)了一半。”冒號(hào)評(píng)論,“動(dòng)態(tài)類型語言固然不需要顯式的變量聲明(explicit declaration,一些靜態(tài)類型語言有時(shí)也不需要。典型的如ML、Haskell之類的函數(shù)式語言,編譯器可以通過上下文來進(jìn)行類型推斷(type inference)。”

          “如何進(jìn)行類型推斷?”問號(hào)有點(diǎn)丈二和尚摸不著頭腦。

          冒號(hào)打了個(gè)比方:“假設(shè)‘+’號(hào)只限于同類型的數(shù)據(jù)運(yùn)算,那么從表達(dá)式a + 1中可以推出a是整型變量,從b + 1.0中推出b是浮點(diǎn)型變量,從c + “1”中推出c是字符串型變量。這些變量不必事先聲明,但一旦類型被推斷確定后,便不再更改。由于這些推斷都是在程序運(yùn)行之前進(jìn)行的,因此仍屬于靜態(tài)類型。它既有動(dòng)態(tài)類型的簡潔性,又不失聲明式靜態(tài)類型的安全性,可謂裁長補(bǔ)短啊。”

          嘆號(hào)有些羨慕地說:“還是動(dòng)態(tài)類型語言好,不僅不必聲明變量,而且一個(gè)變量在不同地方還可以代表不同類型,多省事多方便啊!”

          冒號(hào)微微頷首:“雖然這種機(jī)制也有為人詬病之處,但不可否認(rèn),動(dòng)態(tài)類型語言的確有它的優(yōu)勢(shì):簡明、快捷、靈活,并且天然具有泛型(generic)特征。值得一提的是,動(dòng)態(tài)類型有一種被稱作鴨子類型(duck typing)的形式。”

          逗號(hào)感到有趣:“鴨子類型?很滑稽的名字。”

          “這種類型通俗的說法是:如果一個(gè)對(duì)象既會(huì)走鴨步又會(huì)呷呷叫,何妨將其視作鴨子呢?”冒號(hào)說著投影出一段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)類型的OOP語言中,只要一個(gè)類型具有shout和swim的方法,它就可以為shoutAndSwim所接受。這在C++、Java、C#等靜態(tài)類型語言中是不可能的[4],除非鴨和蛙在同一繼承樹上,或者二者均顯式實(shí)現(xiàn)了一個(gè)包含shout和swim的公用接口。”

          句號(hào)敏銳地指出:“C++是靜態(tài)類型語言,但它的模板也可實(shí)現(xiàn)類似功能,并不需要引入繼承關(guān)系。”

          “說得很對(duì)!但請(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)       #讓一只魚游

          “這里雞沒有swim的方法,魚沒有shout的方法。若采用C++的模板,shoutOrSwim是無法通過編譯的。但在支持Duck 類型的語言中,只要在運(yùn)行期間不讓雞swim、讓魚shout——除非你突發(fā)奇想——一切平安無事。”冒號(hào)作了個(gè)OK的手勢(shì)。

          “動(dòng)態(tài)類型語言真是越看越可愛。”嘆號(hào)簡直垂涎欲滴了。

          “Duck類型的哲學(xué)是:名義不重要,重要的是能力,頗有些實(shí)用主義的味道。這種非繼承性多態(tài)為軟件重用開啟了新的窗口,同時(shí)也埋下了一些陷阱。由于Duck類型的接口組合是隱性的,其使用者需要比普通interface更小心以避免誤用;其維護(hù)者也需要更小心以避免破壞客戶代碼;另外它也可能造成濫用——將一個(gè)會(huì)叫會(huì)游的家伙放進(jìn)池塘看起來不算壞主意,但如果一艘輪船趁機(jī)也轟隆隆地開了進(jìn)來,事情恐怕就不那么美妙了。”

          眾皆莞爾。

          “再來看看靜態(tài)類型語言的好處:由于在運(yùn)行之前進(jìn)行了類型檢查,一方面代碼的可靠性增強(qiáng),符合發(fā)現(xiàn)錯(cuò)誤要盡早的原則;另一方面編譯器有可能藉此優(yōu)化機(jī)器代碼以提高運(yùn)行效率,同時(shí)相比前者節(jié)省了運(yùn)行期的耗費(fèi)在類型檢查上的時(shí)間和空間。此外,變量類型的聲明彰顯了編程者的意圖,有輔助文檔的功效。”冒號(hào)有條有理地解釋著,“兩種類型的體制可以用兩種法律原則來類比:靜態(tài)類型檢查類似‘疑罪從有’的有罪推定制——在被證明合法之前是非法的,動(dòng)態(tài)類型檢查類似‘疑罪從無’的無罪推定制——在被證明非法之前是合法的。至于如何取舍,套用一句話:‘Static Typing Where Possible, Dynamic Typing When Needed’。不妨理解為:盡可能守規(guī)則,必要時(shí)求變通。”

          句號(hào)俏皮地說:“規(guī)則如褲帶,過于寬松和過于束縛都不好。”

          問號(hào)提出新問題:“動(dòng)態(tài)類型語言與弱類型語言有何不同?”

          冒號(hào)喟言:“它們也常常被混為一談,但類型的動(dòng)靜與強(qiáng)弱完全是正交的兩個(gè)概念。靜態(tài)類型語言中,有強(qiáng)類型的Java,也有弱類型的C;動(dòng)態(tài)類型語言中,有強(qiáng)類型的Smalltalk,也有弱類型的JavaScript。前者以類型的綁定(binding)時(shí)間來劃分,后者以類型的約束強(qiáng)度來劃分。通常弱類型語言weakly-typing language)允許一種類型的值隱性轉(zhuǎn)化為另一種類型[5]。舉個(gè)例子,1"2"VB中等于3——第二個(gè)字符串轉(zhuǎn)化為整數(shù);在JavaScript中等于"12"——第一個(gè)整數(shù)轉(zhuǎn)化為字符串;在C中則等于一個(gè)不定的整數(shù)值——第二個(gè)字符串作為地址來運(yùn)算。這樣似乎很有趣很方便,但程序容易藏污納垢,滋生臭蟲(bug)。與此相對(duì)地,強(qiáng)類型語言strongly-typed language)著意貫徹類型控制,為保障數(shù)據(jù)的完整性和代碼的安全有效性,一般不允許隱性類型轉(zhuǎn)換[6]。如果一定需要類型轉(zhuǎn)換,必須是顯性轉(zhuǎn)換,一般通過我們熟知的鑄型cast)來完成。”

          引號(hào)想起:“好像還有一種所謂的類型安全語言?”

          逗號(hào)緊緊抱著頭,仿佛害怕裂開。

          “類型按安全性來劃分,可分為類型安全語言(type-safe language)和類型不安全語言(type-unsafe language)。類型檢查的目的就是為了避免類型錯(cuò)誤(type error[7],即杜絕因類型問題而產(chǎn)生的錯(cuò)誤或不良代碼。如果一個(gè)類型系統(tǒng)能完全做到這一點(diǎn),它就被稱為類型安全的。雖然尚存爭議,但一般認(rèn)為強(qiáng)類型語言對(duì)類型控制更嚴(yán)格,因而是類型安全的,弱類型語言是類型不安全的。類型安全固然對(duì)保障程序的合理性和可靠性十分重要,但若過于嚴(yán)苛,程序也就失去了活力,正所謂‘水至清則無魚’啊。” 冒號(hào)有條不紊地解說著,“至此,我們已論及數(shù)據(jù)類型的三種劃分方式。需要說明的是,這些劃分并非涇渭分明的[8],更多的是定性而非定量的描述,甚至沒有公認(rèn)統(tǒng)一的定義。但了解它們,對(duì)我們理解編程語言和編程原則是大有裨益的。


          插語

          [1] Scala是動(dòng)態(tài)語言,卻是靜態(tài)類型的。

          [2] Visual Basic(不包括VB.NET) 支持動(dòng)態(tài)類型,卻是靜態(tài)語言。

          [3] 極少數(shù)語言沒有類型檢查(untypedtypeless),如大多數(shù)匯編語言、Forth語言等。

          [4] C#4.0將支持duck typing

          [5] 隱式轉(zhuǎn)換也稱為強(qiáng)制轉(zhuǎn)換(coercion)。有人將顯式轉(zhuǎn)換的鑄型(cast)譯為強(qiáng)制轉(zhuǎn)換,并不準(zhǔn)確。

          [6] 但許多強(qiáng)類型語言對(duì)于寬轉(zhuǎn)換(widening conversion)還是允許隱性的,如必要時(shí)int可自動(dòng)轉(zhuǎn)換為float

          [7] 典型的類型錯(cuò)誤是:一個(gè)函數(shù)本來期待的參數(shù)類型是A,實(shí)際傳入的變量a卻不是A類型。

          [8] 比如,靜態(tài)類型的OOP語言如C++Java支持downcasting,能在運(yùn)行期間進(jìn)一步細(xì)化數(shù)據(jù)類型,從某種意義上也具有動(dòng)態(tài)類型的特征。

           

          總結(jié)
          • 盡管動(dòng)態(tài)語言大多數(shù)是動(dòng)態(tài)類型語言,但二者并不是一回事。
          • 數(shù)據(jù)類型包含兩個(gè)要素:允許取值的集合和允許參與的運(yùn)算。
          • 數(shù)據(jù)類型既有針對(duì)機(jī)器的物理意義,又有針對(duì)人的邏輯意義,提高了代碼的安全性和抽象性。
          • 動(dòng)態(tài)類型的類型檢查發(fā)生在運(yùn)行期間,靜態(tài)類型的類型檢查發(fā)生在編譯期間(運(yùn)行之前)。
          • 動(dòng)態(tài)類型的變量不需要顯式聲明,靜態(tài)類型的變量需要通過顯式聲明或類型推斷。
          • 鴨子類型是動(dòng)態(tài)類型的一種風(fēng)格,允許非繼承性多態(tài),即一個(gè)對(duì)象的類型可以由其接口集合來確定,不需要通過顯式繼承。它有利于代碼重用,但也可能造成誤用和濫用。
          • 動(dòng)態(tài)類型語言的優(yōu)點(diǎn):代碼簡明靈活、易于重用,適合泛型編程和快速原型開發(fā)。
          • 靜態(tài)類型語言的優(yōu)點(diǎn):運(yùn)行之前的類型檢查增強(qiáng)了代碼的可靠性,使編譯器有可能進(jìn)行優(yōu)化處理從而提高運(yùn)行效率,節(jié)省了運(yùn)行期的類型檢查所占用的時(shí)間和空間,同時(shí)類型聲明有輔助文檔的功效。
          • 靜態(tài)類型檢查實(shí)行“疑罪從有”的有罪推定制,動(dòng)態(tài)類型檢查實(shí)行“疑罪從無”的無罪推定制。取舍的原則是:Static Typing Where Possible, Dynamic Typing When Needed。即盡可能守規(guī)則,必要時(shí)求變通。
          • 類型的動(dòng)靜以類型的綁定時(shí)間來劃分,類型的強(qiáng)弱以類型的約束強(qiáng)度來劃分,它們之間沒有必然聯(lián)系。弱類型語言允許類型的隱性轉(zhuǎn)化,被認(rèn)為是類型不安全的;而強(qiáng)類型語言則一般不允許這種轉(zhuǎn)化,被認(rèn)為是類型安全的。

           

          “”參考

          [1] WikipediaType systemhttp://en.wikipedia.org/wiki/Type_system

          [2] Erik MeijerPeter DraytonStatic Typing Where Possible, Dynamic Typing When Neededhttp://research.microsoft.com/~emeijer/Papers/RDL04Meijer.pdf

          [3] Ravi SethiProgramming Languages: Concepts & Constructs(英文版第2).北京:機(jī)械工業(yè)出版社,2002136-143

           

          posted on 2009-01-08 00:02 鄭暉 閱讀(3158) 評(píng)論(0)  編輯  收藏 所屬分類: 冒號(hào)課堂

          導(dǎo)航

          統(tǒng)計(jì)

          公告

          博客搬家:http://blog.zhenghui.org
          《冒號(hào)課堂》一書于2009年10月上市,詳情請(qǐng)見
          冒號(hào)課堂

          留言簿(17)

          隨筆分類(61)

          隨筆檔案(61)

          文章分類(1)

          文章檔案(1)

          最新隨筆

          積分與排名

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          主站蜘蛛池模板: 南宫市| 五莲县| 凌云县| 乐东| 哈巴河县| 芜湖县| 连平县| 翁牛特旗| 军事| 弥渡县| 元朗区| 廊坊市| 贺州市| 仁怀市| 高淳县| 黄山市| 四会市| 盐源县| 游戏| 玉门市| 哈密市| 湄潭县| 定南县| 琼中| 中方县| 苏州市| 商南县| 枣庄市| 皋兰县| 海城市| 黄陵县| 肃宁县| 东明县| 仁布县| 南京市| 涟水县| 泰州市| 安远县| 连江县| 崇阳县| 商河县|