解析
編譯器可能使用值的靜態類型以最佳化所需的存儲區,並選取對值運算時的較佳算法。例如,在許多C編譯器中,“浮點數”數據類型是以 32 比特表示,與IEEE 754 規格一致的單精度浮點數。因此,在數值運算上,C 套用了浮點數規範(浮點數加法、乘法等等)。
類型的約束程度以及評估方法,影響了語言的 類型。更進一步,程式語言可能就類型多態性部分,對每一個類型都對應了一個極度個別的算法的運算。類型理論研究類型系統,儘管實際的程式語言類型系統,起源於電腦架構的實際問題、編譯器實現,以及語言設計。
基礎
定型( typing,又稱類型指派)賦予一組比特某個意義。類型通常和存儲器中的數值或對象(如變數)相聯繫。因為在電腦中,任何數值都是以一組比特簡單組成的,硬體無法區分存儲器地址、腳本、字元、整數、以及浮點數。類型可以告知程式和程式設計者,應該怎么對待那些比特。
類型系統提供的主要功能有:
•安全性
使用類型可允許編譯器偵測無意義的,或者是可能無效的代碼。例如,可以識出一個無效的表達式 "Hello, World" + 3,因為不能對(在平常的直覺中)逐字字元串加上一個整數。強類型提供更多的安全性,但它並不能保證絕對安全(詳情請見類型安全)
•最佳化
靜態類型檢查可提供有用的信息給編譯器。例如,如果一個類型指明某個值必須以 4 的倍數對齊,編譯器就有可以使用更有效率的機器指令。
•可讀性
在更具表現力的類型系統中,若其可以闡明程式設計者的意圖的話,類型就可以充當為一種檔案形式。例如,時間戳記可以是整數的子類型;但如果程式設計者聲明一個函式為返回一個時間戳記,而不只是一個整數,這個函式就能表現出一部分檔案的闡釋性。
•抽象化(或模組化)
類型允許程式設計者對程式以較高層次的方式思考,而不是煩人的低層次實現。例如,程式設計者可以將字元串想成一個值,以此取代僅僅是位元組的數組。或者類型允許程式設計者表達兩個子系統之間的接口。將子系統間互動時的必要定義加以定位,防止子系統間的通信發生衝突。
程式通常對每一個值關係一個特定的類型(儘管一個類型可以有一個以上的子類型)。其它的實體,如對象、模組、通信頻道、依賴關係,或者純粹的類型自己,可以和一個類型關係。例如:
•數據類型
一個數值的類型
•類型
一個對象的類型
•種類
一個類型的類型
在每一個程式語言中,都有一個特定的 類型系統,保證程式的表現良好,並且排除違規的行為。作用系統對類型系統提供更多細微的控制。
類型檢查
類型檢查所進行的檢驗處理以及實行類型的約束,可發生在編譯時期(靜態檢查)或運行時期(動態檢查)。靜態類型檢查是在編譯器所進行語義分析中進行的。如果一個語言強制實行類型規則(即通常只允許以不丟失信息為前提的自動類型轉換)就稱此處理為 強類型,反之稱為 弱類型。
動態檢查
如果一個程式語言的類型檢查,可在不測試運行時期表達式的等價性的情況下進行,該語言即為 靜態類型的。一個靜態類型的程式語言,是在運行時期和編譯時期之間的處理階段下重視這些區別的。如果程式的獨立模組,可進行各自的類型檢查(獨立編譯),而無須所有會在運行時出現的模組的那些信息,該語言即具有一個編譯時期階段。如果一個程式語言支持運行時期(動態)調度已標記的數據,該語言即為 動態類型的。如果一個程式語言破壞了階段的區別,因而類型檢查需要測試運行時期的表達式的等價性,該語言即為 依存類型的。
在動態類型中,經常在運行時期進行類型標記的檢查,因為變數所約束的值,可經由運行路徑獲得不同的標記。在靜態類型程式語言中,類型標記使用辨識聯合類型表示。
動態類型經常出現於腳本語言和RAD語言中。動態類型在解譯語言中極為普遍,編譯語言則偏好無須運行時期標記的靜態類型。對於類型和隱式類型語言較完整的列表參見類型和隱式類型語言。
術語 推斷類型(鴨子類型,duck typing)指的是動態類型在語言中的套用方式,它會“推斷”一個數值的類型。
某些靜態語言有一個“後門”,在這些程式語言中,能夠編寫一些不被靜態類型所檢查的代碼。例如,Java 和 C-風格的語言有“轉型”可用。在靜態類型的程式語言中,不必然意味著缺乏動態類型機制。例如 Java 使用靜態類型,但某些運算需要支持運行時期的類型測試,這就是動態類型的一種形式。更多靜態和動態類型的討論,請參閱程式語言。
檢查
對靜態類型和動態類型兩者之間的權衡也是必要的。
靜態類型在編譯時期時,就能可靠地發現類型錯誤。因此通常能增進最終程式的可靠性。然而,有多少的類型錯誤發生,以及有多少比例的錯誤能被靜態類型所捕捉,仍有爭論。靜態類型的擁護者認為,當程式通過類型檢查時,它才有更高的可靠性。雖然動態類型的擁護者指出,實際流通的軟體證明,兩者在可靠性上並沒有多大差別。可以認為靜態類型的價值,在於增進類型系統的強化。強類型語言(如 ML 和 Haskell)的擁護者提出,幾乎所有的臭蟲都可以看作是類型錯誤,如果編寫者以足夠恰當的方式,或者由編譯器推斷來聲明一個類型。
靜態類型通常可以編譯出速度較快的代碼。當編譯器清楚知道所要使用的數據類型,就可以產生最佳化過後的機器碼。更進一步,靜態類型語言中的編譯器,可以更輕易地發現較佳捷徑。某些動態語言(如 Common Lisp)允許任意類型的聲明,以便於最佳化。以上理由使靜態類型更為普及。參閱最佳化。
相較之下,動態類型允許編譯器和解譯器更快速的運作。因為原始碼在動態類型語言中,變更為減少進行檢查,並減少解析代碼。這也可減少編輯-編譯-測試-除錯的周期。
靜態類型語言缺少類型推斷(如 Java),而需要編寫者聲明所要使用的方法或函式的類型。編譯器將不允許編寫者忽略,這可為程式起附加性說明檔案的作用。但靜態類型語言也可以無須類型聲明,所以與其說是靜態類型的代價,倒不如說是類型聲明的報酬。
靜態類型允許構造函式館,它們的用戶不太可能意外的誤用。這可作為傳達庫開發者意圖的額外機制。
動態類型允許建構一些靜態類型系統所做不出來的東西。例如,eval 函式,它使得運行任意數據作為代碼成為可能(不過其代碼的類型仍是靜態的)。此外,動態類型容納過渡代碼和原型設計,如允許使用字元串代替數據結構。靜態類型語言最近的增強(如 Haskell 一般化代數數據類型)允許 eval 函式以類型安全的方式撰寫。
動態類型使元程式設計更為強大,且更易於使用。例如 C++ 模板的寫法,比起等價的 Ruby 或 Python 寫法要來的麻煩。更高度的運行時期構成物,如元類型(metaclass)和內觀(Introspection),對靜態類型語言而言通常更為困難。
強類型弱類型
強類型的基本定義即為,禁止錯誤類型的參數繼續運算。C語言的類型轉換即為缺乏強類型的證例;如果編寫者用 C 語言對一個值轉換類型,不僅令編譯器允許這個代碼,而且在運行時期中也同樣允許。這使得 C 代碼可更為緊密和快速,不過也使除錯變的更為困難。
部分學者使用術語 存儲器安全語言(或簡稱為 安全語言)形容禁止未定義運算發生的語言。例如,某個存儲器安全語言將會檢查數組邊界。
設計精巧的語言也允許語言顯現出弱類型(藉由類型推斷之類的技術)的特性以方便使用,並且保留了強類型語言所提供的類型檢查和保護。例子包括 VBNet、C# 以及 Java。
運算符重載所帶來的簡化,像是不以算術運算中的加法來使用“+”,可以減少一些由動態類型所造成的混亂。例如,部分語言使用“.”或“&”來串連字元串。
安全性
程式語言的類型系統的第三種分類方法,就是類型運算和轉換的安全性。如果它不允許導致不正確的情況的運算或轉換,計算機科學就認為該語言是“類型安全”的。
多態性和類型
術語“多態性”指的是:代碼(尤其是函式和類型)對各種類型的值能夠動作,或是相同數據結構的不同實體能夠控制不同類型的元素。為了提升復用代碼的潛在價值,類型系統逐漸允許多態性:在具有多態性的語言中,程式設計者只需要實現如列表或詞典的數據結構一次,而不是對使用到它的元素的每一個類型都規劃一次。基於這個原因,電腦學家也稱使用了一定的多態性的方法為泛型程式設計。類型理論的多態性基礎與抽象化、模組化和(偶爾)子類型有相當密切的聯繫關係。
推斷類型
推斷類型(鴨子類型,Duck typing)最初是由 Dave Thomas 在 Ruby 社區中提出的,推斷類型用了這個論證法“如果它像什麼,而且其它地方也像什麼,那么它就是什麼。”
在某些程式設計環境中,兩個對象可以有相同的類型,即使它們沒有什麼交集。一個例子是 C++ 在疊代器和指針之間的雙重性。兩者皆以不甚相同的機制實現並提供一個 * 運算。
這個技術之所以常被稱作“鴨子類型”,是基於這句格言:“如果它搖搖擺擺的走法很像鴨子,而且它的嘎嘎叫聲也像鴨子,那它就是一隻鴨子!”
顯示聲明
許多靜態類型系統,如 C 和 Java,要求要 聲明類型:編寫者必須以指定類型明確地關係到每一個變數上。其它的,如 Haskell,則進行類型推斷:編譯器根據編寫者如何運用這些變數,以草擬出關於這個變數的類型的結論。例如,給定一個函式 f(x,y),它將 x 和 y 加起來,編譯器可以推斷出 x 和 y 必須是數字——因為加法僅定義給數字。因此,任何在其它地方以非數值類型(如字元串或鍊表)作為參數來調用 f 的話,將會發出一個錯誤。
隱式暗示
在代碼中數值、字元串常數以及表達式,經常可以在詳細的前後文中暗示類型。例如,一個表達式 3.14 可暗示浮點數類型;而 [1, 2, 3] 則可暗示一個整數的鍊表;通常是一個數組。
類型的類型
類型的類型是一種 種類。在類型程式設計中有明確的種類,如 Haskell 程式語言的 類型構造函式,在申請比較簡單的類型之後,其返回一個簡單的類型。例如,類型構造函式 二選一 有這些種類 * -> * -> *( * 代表種類),而且它的申請 二選一 字元串 整數 是一個簡單的類型。然而,大多數程式語言的類型,是由編寫者來暗示或 硬編碼,這就並未將 種類的概念用作為首選層。
類型可分為幾個大類:
•原始類型
這是最簡單的類型種類,例如:整數和浮點數
•整數類型
全部是數字的類型,例如:整數和自然數
•浮點數類型
以浮點數表示數字的類型
•複合類型
由基本類型組合成的類型,例如:數組或記錄單元。抽象數據類型具有複合類型和界面兩種屬性,這取決於你提及哪一個。
•子類型
•派生類型
•對象類型
例如:變數類型
•不完全類型
•遞歸類型
•函式類型
例如:雙參函式
•全稱量化類型
如參數化類型、類型變數
•存在量化類型
如模組
•精煉類型
識別其它類型的子集的類型
•依存類型
取決於運行時期的數值的類型
•所有權類型
描述或約束面向對象系統結構的類型
兼容性
對於靜態類型語言的類型檢查器,必須檢驗所有表達式的類型,是否與前後文所期望的類型一致。例如指派語句x := e,推斷表達式 e 的類型,必定與聲明或推斷的變數類型 x 一致。這個一致性的概念,就稱為 兼容性,是每一個程式語言所特有的。
很明顯,如果 e 和 x 的類型相同,就允許指派,然後這是一個有效的表達式。因此在最簡單的類型系統中,問題從兩個類型是否兼容,簡化為兩個類型是否 相等(或 等價)。然而不同的語言對於兩個類型表達式是否理解為表示了相同類型,有著不同的標準。類型的 相等理論的差異相當巨大,兩個極端的例子是結構類型系統(Structural type system),任兩個以相同結構所描述的值的類型都是等價的,且在標明類型系統(Nominative type system)上,沒有兩個獨特的語法構成的類型表達式表示同一類型,( 即類型若要相等,就必須具有相同的“名字”)。
在子類型的語言中,兼容關係更加複雜。特別是如果 A 是 B 的子類型,那么類型 A 的值可用於類型 B 也屬意料之中,但反過來就不是這樣。如同等價性,對每一個程式語言而言,子類型的關係的定義是不同的,可能存在各種變化。在語言中出現的參數或者特定的多態性,也可能意味著具有對類型的兼容性。
爭議
在強類型、靜態類型語言的支持者,和動態類型、自由形式的支持者之間,經常發生爭執。前者主張,在編譯的時候就可以較早發現錯誤,而且還可增進運行時期的性能。後者主張,使用更加動態的類型系統,分析代碼更為簡單,減少出錯機會,才能更加輕鬆快速的編寫程式。 與此相關的是,考慮到在類型推斷的程式語言中,通常不需要手動聲明類型,這部分的額外開銷也就自動降低了。