常常聽到有些人誤解了 weak typing 的意義,或是對 C 語言同時是 static typed 與 weak typed 感到疑惑,因此提出一些我自己的想法。
我們先從 type checker 說起吧。
Type Checking
為了要讓人類容易理解並撰寫程式,幾乎所有的高階程式語言都有型別系統(type system)。變數的型別限制我們可以如何操作這個變數,比如說數字可以加減乘除、字串可以串接或截取部份等等。拿數字去做字串操作,或是拿字串去加減乘除[1],都應該視為錯誤操作。[2]
在實作程式語言時,不管是 compiler 或是 interpreter,只要語言中包含型別系統,就會試圖阻止超出型別限制的變數操作,這個功能稱之為 type checking。
|
|
Type checker 可以在 compile 的時候檢查變數的型別,也可以延遲到執行時才進行檢查,這就是 static type-checking 與 dynamic type-checking 的差別。
Static/Dynamic Type-Checking
這兩個名詞通常沒什麼爭議:static type-checking 意指程式中的變數在 compile time 時進行 type checking,而 dynamic type-checking 意指在 run time 才進行 type checking。
|
|
|
|
Weak Typing
而所謂的 weak typing,一般會有兩種解釋方法:
- 語言在遇到 type error 時,會自動轉換型別以符合要求。
- 語言省略了部份的 type checking。
我個人比較傾向第二種解釋方法,以下將會說明為什麼我會選擇後者。
自動型別轉換
為了方便程式設計師,程式語言或多或少會進行自動型別轉換,比如說這個例子:
|
|
有些語言則認為這是不良習慣:
|
|
有些人會藉此主張 Perl 與 C 屬於 weak typing,而 Python 與 Java 屬於 strong typing。然而實際上,自動型別轉換發生在各種地方:
|
|
|
|
大部份的主流語言 (Java、C#、Python、Ruby) 都會進行自動型別轉換,真要符合定義的話,大概要像 Haskell 這樣:
|
|
OCaml 甚至區分了整數加法與浮點數加法:
|
|
嚴格要求明確的型別轉換有好處也有壞處,不過大部份人恐怕都無法習慣 Haskell 或 OCaml 這麼嚴格的規則。即使是以 "Explicit is better than implicit" 為理念的 Python 也仍然在語言上允許相當多的自動(implicit)型別轉換。
我比較不傾向用自動型別轉換來區分程式語言屬於 strong typing 或 weak typing,因為大多數主流語言幾乎都做了自動型別轉換,你頂多只能說某個語言在型別上比另一個語言「強」[3],而不是直接把語言劃分為 strong typing 與 weak typing 兩大塊。
省略 Type Checking
另一個 weak typing 的定義是語言在某些地方會省略 type checking,或是允許程式設計師用某種方法繞過 type checker 的限制。這件事在 C 裡面實在太常見了:
|
|
printf
的函式宣告採用不定參數,因此 compiler 省略了對 x
的型別檢查,其結果為 undefined behavior。另一個例子是 union:
|
|
C 並不會在 union 結構中記錄上一個存入的成員為何,因此也無法在 run time 發出錯誤以阻止使用者取用 u.x
。
相較於自動型別轉型,我認為藉由是否省略 type checking 來區分 weak/strong typing 是比較明確的定義方法。在這個定義下,C 與 C++ 屬於 weak typing,而 Java、C#、Perl、Python、Ruby 屬於 strong typing。