Home
Explore Uedu
Student Console
Register as Member/Login
(2) In future presentations of the research findings, in addition to the course project website and public presentations, your real name and personal information will not appear in this research report. If you are interested in the research results, we can provide you with an executive summary after the study is completed.
問卷中心
Teacher Console
Course Setup
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

0

Local AI

--

CISOSE26 本地 AI UG26
政治大學 AQI 19 29°C

AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

程式語言演進

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

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

為什麼工程師不直接用 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 共讀助教正在陪你讀:程式語言演進:從撥開關到一句話
嗨!我是這篇文章的共讀助教,只根據〈程式語言演進:從撥開關到一句話〉的內容回答。可以問我「解釋某段」「舉個例子」「出題考我」,或反白文中段落後點下方「解釋選取段落」。