只要學過計算機概論,大抵都對編譯與直譯有基本的概念。
常會有人把程式語言分類爲編譯式語言、直譯式語言兩類,其實這並不嚴謹,編譯與直譯並非形容「語言」,而是形容「語言的執行方式」。
舉例來說, C 語言通常被編譯成機器碼執行,但它也可以直譯執行,例如 c4 就是個極簡 C 語言(子集)直譯器,才幾百行而已。
再舉個例子, Kotlin 官方就支援多種執行方式,可以編譯到機器碼、 JVM 字節碼、乃至於轉譯成 JavaScript 再交由 JavaScript 引擎執行。硬要將它歸類為編譯式或直譯式都不對,唯有把程式如何執行在機器上給具體描述出來,才有意義。
基本上,一個語言能被編譯,那幾乎都能被直譯,因為寫直譯器比編譯器要容易得多。而如 Python/Ruby/JavaScript 等動態語言則很難完全被編譯,即使真要編譯,由於動態語言中一個物件是否擁有某個方法是無法在編譯期判定的,所以在編譯出的執行檔中,勢必得加入厚重的運行時來即時維護物件資料、判定物件類型,但如此一來,還能算是編譯嗎?
總的來說,編譯跟直譯是很粗略的分類,要知道一份程式是如何執行的,終究得深入其編譯時跟運行時的細節才行。當吾人說某語言是編譯執行,往往代表其運行時相對較小,反之若說某語言是直譯執行,代表其含有沈重的運行時(通常得附帶整個直譯器)。
帶有沈重運行時的語言難以自舉。以 Python 為例,它的官方實作是用 C 寫的直譯器 CPython。在這個基礎之上,能透過純寫 Python 完成自舉嗎?如果用 Python 寫了個 Python 直譯器,該直譯器仍然得運行在 CPython 上,兩層直譯執行會得到一個很慢的直譯器,而且無法擺脫 CPython。
要擺脫 CPython ,就得是用 Python 寫出 Python 編譯器才行,以此編譯器將自身源碼轉為機器碼,就不再依賴 CPython 了。但前一節提到的物件維護運行時怎麼要怎麼加進去呢?有兩種思路:
不管哪一種做法都需要額外的開發成本。
除了動態語言的物件/類型維護,垃圾搜集也是需要運行時的語言特性。有些語言如 Bash, JavaScript 還支援 eval
這種動態執行程式碼的函數,不把整個直譯器/編譯器包進編譯結果還真支援不了。
編譯時期的語法特性如模式匹配、和類型(sum type)以及各種語法糖都是編譯期(語法、語義分析)就處理完的,跟是否容易自舉的關係不大。當然,語言的能力越強,編譯器越複雜,但語言能力越強,編譯器又越好寫,該如何完成特性的迭代開發,這其中的折衷,大概也是自舉的樂趣所在吧!
TODO: 此節應補圖
小記:本章不少內容屬貧道自行推想,有錯請不吝賜教。