Home
探索 Uedu
學生控制台
註冊會員/登入
研究知情同意中心
教師控制台
課程設定
支援與訊息
Uptime 數據

UeduGPTs

--

Jupyters

4

UG26 CISOSE26
臺北 AQI 46 · 臺中 AQI 28 · 臺南 AQI 24 · 高雄 AQI 33

AI 回覆桌面通知

AI 助教回覆完成時顯示桌面通知

聊天訊息通知

同學在討論區發送訊息時通知

聲音通知

每當有新通知時播放提示音

程式語言演進

程式語言演進:從撥開關到一句話

抽象階梯、編譯與直譯、三大典範,以及編譯器內部那條從詞法到產碼的精密流水線

為什麼工程師不直接用 0 和 1 寫程式?

想像你要請一位完全聽不懂中文、只認得電壓高低的機器幫你算帳。最原始的辦法,是把每一個動作都翻成它認得的二進位指令:10110000 01100001。這是一台 1950 年代的程式設計師真實的日常——他們在紙帶上打孔,或在開關面板上一格一格撥動。寫一個迴圈要查表算位址,改一行程式可能整段位移全錯。

今天,我們寫的是 print("Hello, World!")。從撥開關到一句近乎自然語言的指令,這中間發生的事,正是「程式語言演進」的故事。它不只是語法越來越漂亮,而是人類不斷把「對人友善」與「對機器高效」這兩個互相拉扯的目標,往前推進一點點。

程式設計概念示意圖

從機器碼到高階語言:抽象階梯

語言的演進,本質是一層層往上疊的抽象(abstraction)。每往上一層,人類就少操心一些機器細節。

  • 機器碼(machine code):CPU 直接執行的二進位。完全綁定特定處理器架構,可讀性近乎零。
  • 組合語言(assembly):用助記符(mnemonic)取代二進位,例如 MOVADDJMP。它與機器碼幾乎一對一,但人至少看得懂。仍然高度依賴硬體架構。
  • 高階語言(high-level language):C、Python、Java 這類,用接近數學與自然語言的方式描述邏輯,不再煩惱暫存器(register)分配與記憶體位址。一行高階程式往往對應數十條機器指令。

我們常說 C 是「低階的高階語言」,因為它一方面有變數、函式、迴圈這些高階結構,另一方面又能直接操作記憶體位址(pointer),貼近硬體。Python 則更「高階」:你幾乎不會碰到記憶體管理,代價是執行速度較慢。

這裡有個關鍵取捨:抽象層級越高,開發效率越高,但通常離硬體越遠、執行成本越高。 沒有哪一層絕對更好,只有適不適合當下的問題。寫作業系統核心你會選 C 或 Rust;做資料分析的原型你會選 Python。

編譯與直譯:程式怎麼變成動作

高階語言終究要變成機器能執行的東西。兩條主要路線是編譯(compilation)直譯(interpretation)

比較項目 編譯式 直譯式
代表語言 C、C++、Rust、Go Python、Ruby、JavaScript(傳統)
翻譯時機 執行前一次翻完整支程式 執行時逐行翻譯並執行
產物 可執行檔(executable) 無獨立產物,靠直譯器
執行速度 較慢
跨平台 需各平台分別編譯 同一份程式碼可跨平台(只要有直譯器)
偵錯回饋 編譯期可抓很多錯 多數錯誤要執行到才浮現

實務上界線是模糊的。Java 是個經典混血:原始碼先被編譯成中介的位元組碼(bytecode),再由 Java 虛擬機(JVM) 在執行時直譯或進一步即時編譯。Python 同樣會先把 .py 編成 .pyc 位元組碼,再交給直譯器跑。所以「編譯 vs 直譯」與其說是語言的固有屬性,不如說是實作(implementation)的選擇。

語言典範:解決問題的不同世界觀

除了「怎麼被執行」,語言還在「怎麼組織思考」上分流,這就是程式設計典範(programming paradigm)

程序式(procedural):把程式看成一連串步驟與可呼叫的程序(procedure)。C、Pascal 是代表。思維是「先做這個,再做那個」。

物件導向(object-oriented, OO):把資料與操作該資料的方法綁在一起成為「物件」,用封裝(encapsulation)、繼承(inheritance)、多型(polymorphism)管理大型系統的複雜度。Java、C++、Python 都支援。適合模擬現實世界的實體與關係。

函數式(functional):把運算看成數學函式的求值,強調不可變(immutable)資料與純函式(pure function)——同樣輸入永遠得到同樣輸出、沒有副作用(side effect)。Haskell 是純粹代表,而 map、filter、reduce 這些概念如今已滲入幾乎所有主流語言。它在並行(concurrency)場景特別有優勢,因為沒有共享可變狀態就少了很多競爭問題。

現代語言大多是多典範(multi-paradigm)。Python 既能寫程序式腳本,也能物件導向,還能用 lambda 和高階函式做函數式風格。典範不是非此即彼的信仰,而是工具箱裡不同的扳手。

為什麼世界上有這麼多種程式語言?

學習者常問:既然有了 C,為何還要發明 Rust?既然有 Java,為何還要 Kotlin?原因可以歸納為幾股力量:

  1. 不同問題領域(domain)的需求:統計用 R,系統程式用 C,網頁前端離不開 JavaScript,這些是歷史與生態綁定的結果。
  2. 取捨點不同:安全 vs 速度、開發效率 vs 執行效率、彈性 vs 嚴格。Rust 想要「C 的速度 + 記憶體安全」,於是設計了所有權(ownership)系統。
  3. 修正前人的痛點:Kotlin 之於 Java、TypeScript 之於 JavaScript,都是在既有生態上補強型別安全與語法簡潔。
  4. 硬體與運算模型演進:多核心、GPU、雲端運算催生了對並行與分散式更友善的語言。

換句話說,語言的多樣性不是混亂,而是人類面對不同約束條件下的理性分工

動手看一個例子:同一件事,三種典範

我們用「把一串數字裡的偶數加總」這件小事,看三種典範怎麼表達。

numbers = [1, 2, 3, 4, 5, 6]

# 程序式:明確描述每一步
total = 0
for n in numbers:
    if n % 2 == 0:
        total += n
print(total)  # 12

# 函數式:用 filter 與 sum 描述「是什麼」而非「怎麼做」
total = sum(filter(lambda n: n % 2 == 0, numbers))
print(total)  # 12
# 物件導向:把資料與行為封裝進物件
class NumberBag:
    def __init__(self, items):
        self.items = items

    def sum_even(self):
        return sum(n for n in self.items if n % 2 == 0)

bag = NumberBag([1, 2, 3, 4, 5, 6])
print(bag.sum_even())  # 12

三段程式做的事完全相同,但「程序式」告訴電腦怎麼一步步做,「函數式」描述要的結果是什麼,「物件導向」則把資料與操作組織成一個有意義的單位。選哪種,取決於你要解決的問題規模與性質。

編譯流程的內部解剖

前面說編譯器「把高階程式翻成機器碼」,但這句話藏了一個精密的多階段流水線。理解它,你才真正理解語言如何運作。以 total = a + b * 2; 這一行為例,編譯器大致經過以下階段:

1. 詞法分析(lexical analysis) 把字元流切成有意義的詞元(token)total=a+b*2; 各成一個 token,並標上類別(識別字、運算子、數字字面量等)。負責這件事的程式叫掃描器(scanner)或 lexer。

2. 語法分析(syntax analysis / parsing) 依照語言的文法規則,把 token 串組裝成抽象語法樹(Abstract Syntax Tree, AST)。乘法優先於加法的規則,會反映在樹的結構上:

        =
       / \
   total   +
          / \
         a   *
            / \
           b   2

若 token 排列違反文法(例如少了分號),就在這裡報語法錯誤。

3. 語意分析(semantic analysis) 檢查「文法對、但意義不通」的問題:變數有沒有宣告?型別相不相容?a + ba 是字串而 b 是整數會在這裡被抓出。這階段會建立並查詢符號表(symbol table)

4. 中介碼產生與最佳化(optimization) 編譯器先產生與機器無關的中介表示(Intermediate Representation, IR),再在其上做各種最佳化:把 b * 2 在編譯期就算好(常數摺疊 constant folding)、刪掉永遠用不到的程式碼(dead code elimination)、把迴圈內不變的計算移到迴圈外。目標是讓程式更快或更小,同時保證行為等價

5. 目標碼產生(code generation) 把最佳化後的 IR 翻成特定 CPU 架構的機器碼或組合語言,包含暫存器分配、指令選擇等。

可以把整條流水線想成翻譯一本書:先認字(詞法)、再看懂句子結構(語法)、確認語意通順(語意)、潤飾精煉(最佳化),最後寫成目標語言(產碼)。前端(詞法到語意)與後端(最佳化到產碼)分離的設計,讓同一個前端可以接多個硬體後端——這正是 LLVM 這類現代編譯器框架的核心精神。

即時編譯(JIT):兩條路線的融合

純編譯快但不靈活,純直譯靈活但慢。即時編譯(Just-In-Time compilation, JIT) 試圖兼得兩者之長。

它的做法是:程式先以位元組碼直譯執行,執行期間 JIT 持續觀察哪些程式碼被反覆執行(稱為熱點 hot spot)。一旦某段程式夠「熱」,JIT 就在執行當下把它編譯成原生機器碼,之後再跑到該段就直接執行高速版本。

JIT 的妙處在於它能利用執行期才知道的資訊做最佳化——例如某個多型呼叫實際上幾乎都走同一型別,JIT 可據此做投機最佳化(speculative optimization),這是傳統靜態編譯器做不到的。JVM 的 HotSpot、JavaScript 的 V8 引擎、Python 的 PyPy 都靠 JIT 大幅提速。代價是需要暖機時間(warm-up)與額外記憶體,所以短命的小腳本反而不一定划算。

重點回顧

  • 程式語言的演進,核心是不斷提高抽象層級:從機器碼、組合語言到高階語言,讓人少操心硬體、多專注問題本身,代價通常是執行效率。
  • 編譯與直譯是「翻譯時機」的差異,而非語言的固有本質;Java、Python 等其實是「先編譯成位元組碼、再直譯/JIT」的混血路線。
  • 三大典範——程序式、物件導向、函數式——是組織思考的不同世界觀,現代語言多為多典範,典範是工具而非信仰。
  • 語言多樣性源自領域需求、取捨點、修正前人痛點、硬體演進,是理性分工而非混亂。
  • 編譯器是一條五階段流水線:詞法→語法→語意→最佳化→產碼;而 JIT 在執行期針對熱點動態編譯,融合了編譯與直譯的優點。

深入探討(研究所視角)

形式語言與可計算性的根基。 詞法分析背後是正規語言(regular language)有限狀態自動機(finite automaton),語法分析則對應上下文無關文法(context-free grammar, CFG)下推自動機(pushdown automaton)。這正是 Chomsky 文法層級(Chomsky hierarchy)在編譯器中的具體落實:lexer 用正規表達式可解,parser 需要堆疊(stack)能力。理解這層次,也就理解了「為什麼巢狀括號配對無法只用正規表達式處理」這類經典問題。更上層,所有圖靈完備(Turing-complete)的語言在計算能力上等價——語言之間的差異不在「能算什麼」,而在「表達是否優雅、執行是否高效、錯誤是否易防」。

型別系統與程式正確性。 型別理論(type theory)是程式語言研究最活躍的領域之一。Curry–Howard 同構(Curry–Howard correspondence)揭示了一個深刻對應:型別即命題,程式即證明。一個能通過型別檢查的程式,某種意義上就是一個成立的數學證明。這催生了 Coq、Agda 等依賴型別(dependent type)語言,讓程式正確性可被形式化驗證。Rust 的所有權與借用檢查(borrow checker)則是把線性型別(linear type)的思想工程化,在編譯期消除資料競爭(data race)與懸空指標(dangling pointer),這是把研究成果落地到主流系統語言的範例。

最佳化的理論邊界與 SSA。 現代編譯器最佳化大量建立在靜態單賦值形式(Static Single Assignment, SSA)上:每個變數只被賦值一次,使資料流分析(data-flow analysis)大幅簡化。許多分析本質上是在程式的控制流圖(control-flow graph)上求不動點(fixed point)。然而最佳化有理論天花板——由 Rice 定理(Rice's theorem),程式的多數非平凡語意性質是不可判定(undecidable)的,所以編譯器只能做保守的近似分析,永遠無法「完美最佳化」。

與其他主題的連結。 編譯器將高階語言映射到指令集架構(ISA),是計算機組織作業系統的橋樑;JIT 的熱點偵測本質上是一種輕量級程式剖析(profiling)與線上決策,與系統效能調校相通;而語言的記憶體模型(memory model)直接決定了並行程式在多核心下的正確性,連向分散式系統與並行理論。把「程式語言」當成獨立科目其實低估了它——它是把抽象數學、硬體現實與工程實踐縫合在一起的接點。當你下一次寫下 print("Hello, World!"),背後是七十年抽象階梯與一整個形式科學的支撐。

AI 共讀助教正在陪你讀:程式語言演進:從撥開關到一句話
嗨!我是這篇文章的共讀助教,只根據〈程式語言演進:從撥開關到一句話〉的內容回答。可以問我「解釋某段」「舉個例子」「出題考我」,或反白文中段落後點下方「解釋選取段落」。