類型系統(tǒng)
基礎(chǔ)
定型(typing,又稱類型指派)賦予一組比特某個意義。類型通常和存儲器中的數(shù)值或?qū)ο螅ㄈ缱兞浚┫嗦?lián)系。因為在電腦中,任何數(shù)值都是以一組比特簡單組成的,硬件無法區(qū)分存儲器地址、腳本、字符、整數(shù)、以及浮點數(shù)。類型可以告知程序和程序設(shè)計者,應(yīng)該怎么對待那些比特。
類型系統(tǒng)提供的主要功能有:
安全性
最優(yōu)化
可讀性
抽象化(或模塊化)
程序通常對每一個值關(guān)系一個特定的類型(盡管一個類型可以有一個以上的子類型)。其它的實體,如對象、模塊、通信頻道、依賴關(guān)系,或者純粹的類型自己,可以和一個類型關(guān)系。例如:
數(shù)據(jù)類型
類別
種類
在每一個編程語言中,都有一個特定的類型系統(tǒng),保證程序的表現(xiàn)良好,并且排除違規(guī)的行為。作用系統(tǒng)對類型系統(tǒng)提供更多細微的控制。
類型
e:int{\displaystyle e:int}:斷言變量e的類型是int。
類型檢查
類型檢查所進行的檢驗處理以及實行類型的約束,可發(fā)生在編譯時期(靜態(tài)檢查)或運行時期(動態(tài)檢查)。靜態(tài)類型檢查是在編譯器所進行語義分析中進行的。如果一個語言強制實行類型規(guī)則(即通常只允許以不丟失信息為前提的自動類型轉(zhuǎn)換)就稱此處理為強類型,反之稱為弱類型。
靜態(tài)和動態(tài)檢查
如果一個編程語言的類型檢查,可在不測試運行時期表達式的等價性的情況下進行,該語言即為靜態(tài)類型的。一個靜態(tài)類型的編程語言,是在運行時期和編譯時期之間的處理階段下重視這些區(qū)別的。如果程序的獨立模塊,可進行各自的類型檢查(獨立編譯),而無須所有會在運行時出現(xiàn)的模塊的那些信息,該語言即具有一個編譯時期階段。如果一個編程語言支持運行時期(動態(tài))調(diào)度已標記的數(shù)據(jù),該語言即為動態(tài)類型的。如果一個編程語言破壞了階段的區(qū)別,因而類型檢查需要測試運行時期的表達式的等價性,該語言即為依存類型的。
在動態(tài)類型中,經(jīng)常在運行時期進行類型標記的檢查,因為變量所約束的值,可經(jīng)由運行路徑獲得不同的標記。在靜態(tài)類型編程語言中,類型標記使用辨識聯(lián)合類型表示。
動態(tài)類型經(jīng)常出現(xiàn)于腳本語言和RAD語言中。動態(tài)類型在解譯語言中極為普遍,編譯語言則偏好無須運行時期標記的靜態(tài)類型。對于類型和隱式類型語言較完整的列表參見類型和隱式類型語言。
術(shù)語推斷類型(鴨子類型,duck typing)指的是動態(tài)類型在語言中的應(yīng)用方式,它會“推斷”一個數(shù)值的類型。
看看類型標記檢查是如何運作的,考慮下列假碼示例:
在這個示例中,(1)宣告x;(2)將整數(shù)值5代給x;(3)將字符串值"hi"代給x。在主要的靜態(tài)系統(tǒng)中,這個代碼片斷將會違反規(guī)則,因為(2)和(3)對 x所約束的類型相矛盾。
相較之下,一個純粹的動態(tài)類型系統(tǒng)允許上述程序的運行,因為類型標記附到數(shù)值上(不是變量)。在處理錯誤語句或表達式的時候,以動態(tài)類型實現(xiàn)的語言會捕捉程序的錯誤,而不是誤用錯誤類型的數(shù)值。換句話說,動態(tài)類型捕捉在程序運行時的錯誤。
典型的動態(tài)類型實現(xiàn),會以類型標記維持程序所有數(shù)值的“標記”,并在運算任何數(shù)值之前檢查標記。例如:
在這個程序片斷中,(1)將數(shù)值5約束給x;(2)將數(shù)值"hi"約束給y;以及(3)嘗試將x加到y(tǒng)。在動態(tài)類型語言中,約束給x的值會是一對(整數(shù), 5),且約束給y的值會是一對(字符串, "hi")。當這個程序嘗試運行第3行時,語言對類型標記整數(shù)和字符串進行檢查,如果這兩個類型的+(加法)運算尚未定義,就會發(fā)出一個錯誤。
某些靜態(tài)語言有一個“后門”,在這些編程語言中,能夠編寫一些不被靜態(tài)類型所檢查的代碼。例如,Java和C-風(fēng)格的語言有“轉(zhuǎn)型”可用。在靜態(tài)類型的編程語言中,不必然意味著缺乏動態(tài)類型機制。例如Java使用靜態(tài)類型,但某些運算需要支持運行時期的類型測試,這就是動態(tài)類型的一種形式。更多靜態(tài)和動態(tài)類型的討論,請參閱編程語言。
實踐中的靜態(tài)和動態(tài)類型檢查
對靜態(tài)類型和動態(tài)類型兩者之間的權(quán)衡也是必要的。
靜態(tài)類型在編譯時期時,就能可靠地發(fā)現(xiàn)類型錯誤。因此通常能增進最終程序的可靠性。然而,有多少的類型錯誤發(fā)生,以及有多少比例的錯誤能被靜態(tài)類型所捕捉,目前對此仍有爭論。靜態(tài)類型的擁護者認為,當程序通過類型檢查時,它才有更高的可靠性。雖然動態(tài)類型的擁護者指出,實際流通的軟件證明,兩者在可靠性上并沒有多大差別??梢哉J為靜態(tài)類型的價值,在于增進類型系統(tǒng)的強化。強類型語言(如ML和Haskell)的擁護者提出,幾乎所有的臭蟲都可以看作是類型錯誤,如果編寫者以足夠恰當?shù)姆绞?,或者由編譯器推斷來宣告一個類型。
靜態(tài)類型通??梢跃幾g出速度較快的代碼。當編譯器清楚知道所要使用的數(shù)據(jù)類型,就可以產(chǎn)生最優(yōu)化過后的機器碼。更進一步,靜態(tài)類型語言中的編譯器,可以更輕易地發(fā)現(xiàn)較佳快捷方式。某些動態(tài)語言(如Common Lisp)允許任意類型的宣告,以便于最優(yōu)化。以上理由使靜態(tài)類型更為普及。參閱最優(yōu)化。
相較之下,動態(tài)類型允許編譯器和解譯器更快速的運作。因為源代碼在動態(tài)類型語言中,變更為減少進行檢查,并減少解析代碼。這也可減少編輯-編譯-測試-除錯的周期。
靜態(tài)類型語言缺少類型推斷(如Java),而需要編寫者宣告所要使用的方法或函數(shù)的類型。編譯器將不允許編寫者忽略,這可為程序起附加性幫助文檔的作用。但靜態(tài)類型語言也可以無須類型宣告,所以與其說是靜態(tài)類型的代價,倒不如說是類型宣告的報酬。
靜態(tài)類型允許構(gòu)造函數(shù)庫,它們的用戶不太可能意外的誤用。這可作為傳達庫開發(fā)者意圖的額外機制。
動態(tài)類型允許建構(gòu)一些靜態(tài)類型系統(tǒng)所做不出來的東西。例如,eval函數(shù),它使得運行任意數(shù)據(jù)作為代碼成為可能(不過其代碼的類型仍是靜態(tài)的)。此外,動態(tài)類型容納過渡代碼和原型設(shè)計,如允許使用字符串代替數(shù)據(jù)結(jié)構(gòu)。靜態(tài)類型語言最近的增強(如Haskell 一般化代數(shù)數(shù)據(jù)類型)允許eval函數(shù)以類型安全的方式撰寫。
動態(tài)類型使元程序設(shè)計更為強大,且更易于使用。例如C++模板的寫法,比起等價的Ruby或Python寫法要來的麻煩。更高度的運行時期構(gòu)成物,如元類別(metaclass)和內(nèi)觀(Introspection),對靜態(tài)類型語言而言通常更為困難。
強類型和弱類型
強類型的基本定義即為,禁止錯誤類型的參數(shù)繼續(xù)運算。C語言的類型轉(zhuǎn)換即為缺乏強類型的證例;如果編寫者用C語言對一個值轉(zhuǎn)換類型,不僅令編譯器允許這個代碼,而且在運行時期中也同樣允許。這使得C代碼可更為緊密和快速,不過也使除錯變的更為困難。
部分學(xué)者使用術(shù)語存儲器安全語言(或簡稱為安全語言)形容禁止未定義運算發(fā)生的語言。例如,某個存儲器安全語言將會檢查數(shù)組邊界。
弱類型意指一個語言可以隱式的轉(zhuǎn)換類型(或直接轉(zhuǎn)型)??纯聪惹暗睦樱?/span>
在弱類型語言中編寫上述代碼,并不清楚將會得到哪一種結(jié)果。某些語言如Visual Basic,將會產(chǎn)生可以運作的代碼,它將會給出的結(jié)果是42:系統(tǒng)將字符串"37"轉(zhuǎn)換成數(shù)字37,以匹配運算上的直覺;其它的語言,像JavaScript將會產(chǎn)生的結(jié)果是"537":系統(tǒng)將數(shù)字5轉(zhuǎn)換成字符串"5"并把兩者串接起來。在Visual Basic和JavaScript中,最終的類型是以那兩個運算對象為考量的規(guī)則所決定。在部分語言中,如AppleScript,某個值最終的類型,只以最左邊的運算對象的類型所決定。
設(shè)計精巧的語言也允許語言顯現(xiàn)出弱類型(借由類型推斷之類的技術(shù))的特性以方便使用,并且保留了強類型語言所提供的類型檢查和保護。例子包括VB.Net、C#以及Java。
運算符重載所帶來的簡化,像是不以算術(shù)運算中的加法來使用“+”,可以減少一些由動態(tài)類型所造成的混亂。例如,部分語言使用“.”或“&”來串連字符串。
類型系統(tǒng)的安全性
編程語言的類型系統(tǒng)的第三種分類方法,就是類型運算和轉(zhuǎn)換的安全性。如果它不允許導(dǎo)致不正確的情況的運算或轉(zhuǎn)換,計算機科學(xué)就認為該語言是“類型安全”的。
再次看看這個假碼例子:
在一個如Visual Basic的語言中,例子中的變量z得到的值為42。不管編寫者有沒有這個意圖,該語言定義了明確的結(jié)果,且程序不會就此崩潰,或?qū)⒉幻鞫x的值賦給z。就這方面而言,這樣的語言就是類型安全的。
現(xiàn)在來看C的相同例子:
在這個例子中,z將會指向一個超過y地址5個字節(jié)的存儲器地址,相當于指向y字符串的指針之后的兩個空字符之處。這個地址的內(nèi)容尚未定義,且有可能超出存儲器的定址界線,而且就這么引用參考z會引起程序的終止。雖是一個良好類型,但卻不是存儲器安全的程序——如果以對類型安全語言而言不該發(fā)生為先決條件的話。
多態(tài)性和類型
術(shù)語“多態(tài)性”指的是:代碼(尤其是函數(shù)和類別)對各種類型的值能夠動作,或是相同數(shù)據(jù)結(jié)構(gòu)的不同實體能夠控制不同類型的元素。為了提升復(fù)用代碼的潛在價值,類型系統(tǒng)逐漸允許多態(tài)性:在具有多態(tài)性的語言中,程序設(shè)計者只需要實現(xiàn)如列表或詞典的數(shù)據(jù)結(jié)構(gòu)一次,而不是對使用到它的元素的每一個類型都規(guī)劃一次?;谶@個原因,電腦學(xué)家也稱使用了一定的多態(tài)性的方法為泛型程序設(shè)計。類型理論的多態(tài)性基礎(chǔ)與抽象化、模塊化和(偶爾)子類型有相當密切的聯(lián)系關(guān)系。
推斷類型
推斷類型(鴨子類型,Duck typing)最初是由Dave Thomas在Ruby社區(qū)中提出的,推斷類型用了這個論證法“如果它像什么,而且其它地方也像什么,那么它就是什么?!?/span>
在某些程序設(shè)計環(huán)境中,兩個對象可以有相同的類型,即使它們沒有什么交集。一個例子是C++在迭代器和指針之間的雙重性。兩者皆以不甚相同的機制實現(xiàn)并提供一個* 運算。
這個技術(shù)之所以常被稱作“鴨子類型”,是基于這句格言:“如果它搖搖擺擺的走法很像鴨子,而且它的嘎嘎叫聲也像鴨子,那它就是一只鴨子!”
顯示宣告和隱式暗示
許多靜態(tài)類型系統(tǒng),如C和Java,要求要宣告類型:編寫者必須以指定類型明確地關(guān)系到每一個變量上。其它的,如Haskell,則進行類型推斷:編譯器根據(jù)編寫者如何運用這些變量,以草擬出關(guān)于這個變量的類型的結(jié)論。例如,給定一個函數(shù)f(x,y),它將x和y加起來,編譯器可以推斷出x和y必須是數(shù)字——因為加法僅定義給數(shù)字。因此,任何在其它地方以非數(shù)值類型(如字符串或鏈表)作為參數(shù)來調(diào)用f的話,將會發(fā)出一個錯誤。
在代碼中數(shù)值、字符串常數(shù)以及表達式,經(jīng)常可以在詳細的前后文中暗示類型。例如,一個表達式3.14可暗示浮點數(shù)類型;而[1, 2, 3]則可暗示一個整數(shù)的鏈表;通常是一個數(shù)組。
類型的類型
類型的類型是一種種類。在類型程序設(shè)計中有明確的種類,如Haskell編程語言的類型構(gòu)造函數(shù),在申請比較簡單的類型之后,其返回一個簡單的類型。例如,類型構(gòu)造函數(shù)二選一有這些種類* -> * -> *(*代表種類),而且它的申請二選一字符串整數(shù)是一個簡單的類型。然而,大多數(shù)編程語言的類型,是由編寫者來暗示或硬編碼,這就并未將種類的概念用作為首選層。
類型可分為幾個大類:
原始類型
整數(shù)類型
浮點數(shù)類型
復(fù)合類型
子類型
派生類型
對象類型
不完全類型
遞歸類型
函數(shù)類型
全稱量化類型
存在量化類型
精煉類型
依存類型
所有權(quán)類型
兼容性:等價性和子類型
對于靜態(tài)類型語言的類型檢查器,必須檢驗所有表達式的類型,是否與前后文所期望的類型一致。例如指派語句x := e,推斷表達式e的類型,必定與宣告或推斷的變量類型x一致。這個一致性的概念,就稱為兼容性,是每一個編程語言所特有的。
很明顯,如果e和x的類型相同,就允許指派,然后這是一個有效的表達式。因此在最簡單的類型系統(tǒng)中,問題從兩個類型是否兼容,簡化為兩個類型是否相等(或等價)。然而不同的語言對于兩個類型表達式是否理解為表示了相同類型,有著不同的標準。類型的相等理論的差異相當巨大,兩個極端的例子是結(jié)構(gòu)類型系統(tǒng)(Structural type system),任兩個以相同結(jié)構(gòu)所描述的值的類型都是等價的,且在標明類型系統(tǒng)(Nominative type system)上,沒有兩個獨特的語法構(gòu)成的類型表達式表示同一類型,(即類型若要相等,就必須具有相同的“名字”)。
在子類型的語言中,兼容關(guān)系更加復(fù)雜。特別是如果A是B的子類型,那么類型A的值可用于類型B也屬意料之中,但反過來就不是這樣。如同等價性,對每一個編程語言而言,子類型的關(guān)系的定義是不同的,可能存在各種變化。在語言現(xiàn)的參數(shù)或者特定的多態(tài)性,也可能意味著具有對類型的兼容性。
爭議
在強類型、靜態(tài)類型語言的支持者,和動態(tài)類型、自由形式的支持者之間,經(jīng)常發(fā)生爭執(zhí)。前者主張,在編譯的時候就可以較早發(fā)現(xiàn)錯誤,而且還可增進運行時期的性能。后者主張,使用更加動態(tài)的類型系統(tǒng),分析代碼更為簡單,減少出錯機會,才能更加輕松快速的編寫程序。與此相關(guān)的是,考慮到在類型推斷的編程語言中,通常不需要手動宣告類型,這部分的額外開銷也就自動降低了。
參閱
運算符重載
面向?qū)ο蟪绦蛟O(shè)計中的多態(tài)
編程語言
Type signature
Signedness
類型系統(tǒng)參考表
免責(zé)聲明:以上內(nèi)容版權(quán)歸原作者所有,如有侵犯您的原創(chuàng)版權(quán)請告知,我們將盡快刪除相關(guān)內(nèi)容。感謝每一位辛勤著寫的作者,感謝每一位的分享。
- 有價值
- 一般般
- 沒價值
{{item.userName}} 舉報
{{item.time}} {{item.replyListShow ? '收起' : '展開'}}評論 {{curReplyId == item.id ? '取消回復(fù)' : '回復(fù)'}}
{{_reply.userName}} 舉報
{{_reply.time}}