一次搞懂動態類型/靜態類型、強類型/弱類型

第一堂資訊課時,電腦老師總會(應該吧)說「C++ 是靜態 & 弱類型語言、Python 是動態 & 強類型語言」。

但 C++ 又哪裏「靜態」?Python 又為何是「強類型」?

這篇文章,我想談談「動態/靜態類型」、「強/弱」語言之間的差異。

動態類型 & 靜態類型

所有語言皆可分成兩種:動態類型(Dynamic Typed)語言靜態類型(Statically Typed)語言

動態類型(Dynamic Typed)

x = 1
# 這時候 x 的 type 是 int,
print(1) # 1

x = "hello"
# 這時候 x 的 type 是 str
print(x) # x

在動態類型語言中,一個變數的 type 可以反覆更改。在上面 Python 的範例中,x 一開始的 type 是 int。經過更改後,type 則變成 str

動態類型語言有 Python、JavaScript、Ruby、Lua、PHP、Perl、Racket/Scheme 等。

幾乎所有的動態語言都經由直譯執行,只有少數語言(例如 Common Lisp 和 Racket)可以直接編譯成執行檔。

優點

因爲不必手動標記類型 & 確定變數類型,所以很容易糊出一個程式的原型。不少公司的產品最初都由動態語言編寫,而後才轉到靜態語言。

Facebook 最初是由 PHP 寫成,後來才陸續有了 C++、Java 等其他語言。

因爲對 function 的參數沒有類型要求,所以動態語言容易實現「泛型」。

def add(a, b):
    return a + b
int add(int a, int b) {
    return a + b
}

double add(double a, double b) {
    return a + b
}

靜態類型(Statically Typed)

int main() {
    int n = 2;
    n = "String" // 報錯,n 不能賦值爲 "String"
}

相較之下,靜態語言的變數類型在初始化時便決定,之後也無法再度更改。

早期的靜態語言(C、C++、Java)必須手動寫出類型標記(範例中的 int),但近二十年出現的靜態語言(C#、Kotlin、Rust、Swift)都支持類型推導(Type Inference),編譯器會幫你判斷變數的類型。

比如說在 Rust 能這樣寫:

fn main() {
    let x = 20; // Rust 知道 x 的類型是 i32
    println!("{}", x);
}

常見的靜態語言有 C/C++、Java、C#、Rust、Swift、Haskell、OCaml、Kotlin、Golang 等。

大部分的靜態語言都採用編譯執行,不過也有一些語言(如 OCaml)可以直譯執行。

優點

因爲變數的類型無法更改,所以編譯器能提前找出可能的類型錯誤。

因此大部分大公司的大 project 都由靜態語言寫成。

因爲靜態語言容易編譯成執行檔,速度較多數採直譯的動態語言快。

因爲編譯器可以由程式中推出變數的類型、一個 class 有哪些函數,所以普遍而言 IDE 對靜態語言的支持較好。

不過這點在 Microsoft 搞出 LSP 後差距減少不少。