天天看點

采訪 Lua 發明人的一篇文章

采訪 Lua 發明人的一篇文章 

書是好書,可惜翻譯這本書需要對各種語言的深入研究,看起來譯者有點力不從心。出版社打算重新做這本書。受編輯所托,我校對了其中第七章:有關 Lua 的一段。原文讀下來拍案叫好。可惜譯文許多地方看起來有些詞不達意。許多在口語化交流中提到的術語被忽略了做了錯誤的翻譯。有些部分應該是對 lua 了解不夠而沒能表達清楚。

仔細校對了兩段後,我幹脆放棄原譯本,自己動手翻譯了一份(保留了不到 1/4 原來的譯文)。雖然個人能力有限,但也算是每句話自己都看明白了再譯的。雖說有些地方沒有直譯,但也算沒有夾帶私貨。

這裡貼出一段,希望大家閱讀愉快。

Lua 是一門非常之小,但五髒俱全的動态語言。它由 Roberto Ierusalimschy、Luiz Henrique de Figueiredo 和 Waldemar Celes在1993年建立。Lua 擁有一組精簡的強大特性,以及容易使用的 C API ,這使得它易于嵌入與擴充來表達特定領域的概念。Lua在專有軟體界聲名顯赫。例如,在諸多遊戲中,比如 Blizzard(暴雪)公司的《魔獸世界》和 Crytek GmbH 公司的《孤島危機》,還有 Adobe 的 Photoshop Lightroom ,都使用它來作腳本 和 UI 方面的工作。它繼承了 Lisp 和 Scheme,或許還有 AWK 的血脈 ; 在設計上類似于 JavaScript、Icon 和 Tcl。

你是如何定義 Lua 的?

LHF:一種可嵌入,輕量,快速,功能強大的腳本語言。

Roberto:不幸的是,越來越多的人們使用“腳本語言”作為“動态語言”的代名詞。現在,甚至是 Erlang 或者 Scheme 都被稱為腳本語言。這非常糟糕,因為我們無法精确的描述一類特定的動态語言。在最初的含義解釋中,Lua 是一種腳本語言,這種語言通常用來控制其它語言編寫的其他元件。

人們在使用Lua設計軟體時,應該注意些什麼呢?

Luiz:我想應該是用 Lua 的方式來做事。不建議去模拟出所有你在其它語言中用到的東西。你應該真的去用這個語言提供的特性,我想對于使用任何一門語言都是這樣的。就 Lua 來講,語言的特性主要指用 table 表示所有的東西,用 metamethod 做出優雅的解決方案。還有 coroutine 。

Lua 的使用者應該是哪些人呢?

Roberto :我認為大多數沒有腳本功能的應用程式都能從 Lua 中受益。

Luiz:問題在于,大多數設計者很長時間都不會意識到有這種需求。當已經有了諸多用 C 或 C++ 編寫的代碼,為時已晚。應用程式設計者應該從一開始就考慮腳本。這會給它們帶來更多的靈活性。而且這樣做還可以更好的把握性能問題。因為這樣做以後,會迫使他們去考慮程式中到底哪裡是性能關鍵,而哪些地方無傷大雅。而這些性能不太重要之處,就交給腳本去處理,開發周期短,速度快。

從安全性的觀點來看,Lua 能為程式員提供些什麼呢?

Roberto:Lua 解釋器的核心部分被建構為一個 “獨立的應用程式(freestanding application)”。這個術語來自 ISO C,大意是說,這部分不使用任何跟外部環境有關的東西(不依賴 stdio、malloc 等)。所有那些功能都由擴充庫來提供。使用這種體系結構,很容易讓程式限制對外部資源的通路。具體來說,我們可以在 Lua 自身的内部建立出一個沙盒,把如何我們認為危險的操作從沙盒的外部環境中剔除。(比如打開檔案等)

Luiz:Lua 還提供了使用者自定義的調試鈎子,用它可以監視 Lua 程式的執行。這樣,在 lua 中運作時間過長或是使用了過多記憶體的時候,我們可以從外部中斷它的執行。

Lua 有什麼局限性?

Roberto:我認為 Lua 的主要局限是所有動态語言共有的。首先,即使是利用最先進的 JIT 技術(Lua 的 JIT 是所有動态語言 JIT 中最好的之一)也達不到優秀靜态語言的性能。其次,一些複雜的程式從靜态分析中受益匪淺(主要是靜态類型)。

是什麼促使你決定使用垃圾收集器?

Roberto:Lua 從第一天開始,就一直使用垃圾收集器。我想說,對于一種解釋型語言來講,垃圾收集器可以比引用計數更加緊湊和健壯,更不用說它沒有把垃圾丢得到處都是。考慮到解釋型語言通常已經有自描述資料(通過給值加上标簽之類的東西),一個簡單的标記清除(mark-and-sweep)收集器實作起來極其簡單,而且幾乎對解釋器其餘的部分不會産生什麼影響。

對于無類型語言(untyped language),引用計數會很重量。沒有靜态類型,每次指派都可能會改變計數,對變量的新值和舊值都需要進行動态檢查。後來嘗試過在 Lua 中引入引用計數,并沒有提高性能。

你對 Lua 處理數字的方式滿意嗎?

Roberto:從我的經驗來看,計算機中的數字老是會給我們帶來一些意外(因為它們也來至于計算機之外!)。至于說 Lua 使用 double 作為唯一的數字類型,我認為這是一種合理的折衷方案。我們已經考慮了很多其他可選方案,不過對于 Lua 來說,這些方案要麼太慢,要麼太複雜,要麼太耗記憶體。對于嵌入式系統,甚至使用 double 也不是一種合理的選擇,是以,我們可以使用一個備選的數值類型,比如說 long ,來編譯解釋器。

你為什麼選擇 table 作為 Lua 中的統一資料結構?

Roberto:從我的角度,靈感來自于VDM(一個主要用于軟體規範的形式化方法),當我們開始建立 Lua 時,有一些東西吸引了我的興趣。VDM 提供三種資料聚合的方式:set、sequence 和 map。不過,set 和 sequence 都很容易用 map 來表達,是以我有了用 map 作為統一結構的想法。Luiz 也有他自己的原因。

Luiz:沒錯,我非常喜歡 AWK ,特别是它的聯合數組。

程式員可以從 Lua 中的 first-class 函數中獲得怎樣的價值?

Roberto:50多年來,雖然名稱各異:從子程式到方法,“函數” 已經成為程式設計語言的主要部分,是以,對函數的良好支援為所有語言必備。Lua 支援程式員使用函數式程式設計領域中的一些功能強大的技術,比如,把資料表示成函數。例如,一種形狀可能用函數來表示,給定 x 和 y ,可以判斷這個點是否在這個形狀内。這種表示方式可以用于一些操作,比如聯合和交集等。

你為什麼要實作閉包 ( closure ) ?

Roberto:閉包自始至終我們都想在 Lua 中實作:它簡單、靈活、功能強大。從第一版開始,Lua 就把函數做為一等值 ( first-class value ) 對待。這被證明非常有用,即使是對于沒有函數式程式設計的“正常的”程式員來說也是一樣。而不支援閉包的函數,其實用價值就會大打折扣。順便說一句,閉包這個術語來源于一種實作技術,而不是指它本身的特性。從特性描述上來說,閉包相當于“帶詞法作用域的一等函數”,當然用閉包這個術語更為簡短。

你打算如何處理并發問題?

Roberto:我們不信任基于搶占式記憶體共享的多線程技術。在 HOPL 論文中,我們寫道:“我們仍然認為,如果在連 a=a+1 都沒有确定結果的語言中,無人可以寫出正确的程式。” 我們可以通過去掉搶占式這一點,或是不共享記憶體,就可以回避這個問題。而 Lua ,提供用這兩種方式解決問題的支援。

使用協程(coroutine),我們可以共享記憶體,但不是搶占式的。不過這個技術利用不到多核機器。但在這類機器上,使用多“程序”就能極大的發揮其性能。這個我提到的“程序”是指在 C 裡的一個線程,這個線程維護自己獨立的 Lua 狀态機。這樣,在 Lua 層面上,就沒有記憶體共享使用。在《Lua 程式設計第二版》[Lua.org] 中,我給出了這種方式的一個原型。最近我們已經看到有些庫支援了這種方式(比如 Lua Lanes 以及 luaproc)。

你沒有支援并發,但你為多任務實作了一個有趣的解決方案:非對稱式協程。它們如何工作的?

Roberto:我有一些 Modula 2 語言的經驗(我的妻子在她的碩士論文工作中為 M-code 編寫了一個完整的解釋器),使用協程作為協作式并發以及别的控制結構的基礎設定是我一直偏愛的方法。然而,Modula 2 中那種對稱式協程,在 Lua 中行不通。

Luiz:在我們的 HOPL 論文中,對那些設計決策全部做了極為詳細的解釋說明。

Roberto:我們最終選擇了非對稱式模型。它的基本思想非常簡單。通過顯式調用 coroutine.create 函數來建立一個協程,把一個函數作為協程主體來執行。當我們啟動 (resume) 協程時,它開始運作函數體并且直到結束或者讓出控制權 (yield) ;一個協程隻有通過顯式調用 yield 函數才會中斷。以後,我們可以 resume 它,它将會從它停止的地方繼續執行。

它的基本思想非常類似于 Python 的生成器,但有一個關鍵差別:Lua協程可以在嵌套調用中 yield,而在 Python 中,生成器隻能從它的主函數中 yield。在實作上,這意味着每個協程像線程一樣必須有獨立堆棧。和“平坦”的生成器相比,“帶堆棧”的協程發揮了不可思議的強大威力。例如,我們可以在它們的基礎上實作一次性延續點 (one-shot continuations)。

對于你做的這些,你如何定義成功?

Luiz:一種語言的成功,取決于使用該語言的程式員數量以及使用它的應用程式的成功。其實,到底有多少人在使用 Lua 程式設計,我們并沒有确切的答案,不過毫無疑問的是,有很多成功使用 Lua 的應用程式,其中包括一些非常成功的遊戲。同樣地,使用 Lua 的應用程式的範圍,從桌面圖像處理到嵌入式機器人控制。這表明 Lua 具有一個非常明确的小衆市場。最後,Lua 是唯一一種由開發中國家建立并在全球獲得廣泛應用的語言。它也是 ACM HOPL 唯一重點推介的語言。

Roberto:這很難定義。我曾經在多個領域工作過,在每個領域我從不同的方式在感受了成功。總之,我想說這些的共通之處在于:“被人知曉”。被認可,被公認,被人們推薦,這些都讓人非常開心。

對于這門語言,你有什麼遺憾嗎?

Luiz:我确實沒有任何遺憾。不過,事後回想起來,如果我們當初知道我們現在正在做的事情該怎麼做的話,這些事情本可以早點完成!

Roberto:我不确信我有什麼具體的遺憾,不過語言設計會牽涉到很多困難的決策。對我來說,最困難的決策是在易用性方面。Lua 的目标之一是讓非專業程式員易于使用。我沒有契合這種定位。是以,當我把自己當作使用者,從這個視野來看,有關 Lua 語言的某些決策并非最佳。Lua 的文法就是一個典型的例子:雖然 Lua 的很多應用都得益于其冗長的文法,不過,就我自己的口味而言,我更偏愛緊湊的符号。

你在設計或實作時犯過錯嗎?

Luiz:我認為我們在設計或實作 Lua 時,并沒有犯什麼大錯。我們學着如何發展一門語言。這絕不僅僅是定義它的文法和語義并将其實作。還有許多重要的社會問題,比如說建立并支援一個社群。這需要通過多種途徑,編撰手冊、寫書、維護網站、郵件清單以及聊天室等。毫無疑問,我們認識到了支援一個社群的價值,明白了做這些工作需要極大的投入,并不亞于在設計和編碼工作中的投入。

Roberto:我們很幸運,沒有犯什麼大錯。我們在這個過程中還是出了許多小問題。作為 Lua 演化發展的一部分,我們有機會修正它們。當然,版本間的不相容問題會讓一些使用者感到煩惱。好在 Lua 現在已經非常穩定了。

對于成為一名優秀的程式員,你有什麼建議?

Luiz:永遠不要害怕重新開始,這當然是說到容易做到難。永遠不要低估需要注意的細節。你認為未來可能會用到的功能,就不要馬上添加了:現在增加這個功能隻會讓你日後真的需要這個東西時,那些更好的特性很難加入。最後,永遠追求更為簡潔的解決方案。誠如愛因斯坦所言:盡量簡潔,然過猶不及 ( As simple as possible, but not simpler. )。

Roberto:學習新的程式設計語言,不過一定要讀好書!Haskell 是所有程式員都應該學會的一種語言。學習計算機科學:新算法、新形式體系(如果你還不了解,可以看一下 Lambda 演算,或是 pi 演算,CSP 等等)持續改進你的代碼。

計算機科學的最大問題是什麼?我們又如何教授呢?

Roberto:我想還沒有什麼能像“計算機科學”那樣表達一種廣為人知的知識集。并不是說計算機科學不是科學,而是說太難定義什麼是計算機科學,什麼不是(以及什麼重要什麼不重要)。計算機科學界的很多人都沒有一個正規的計算機科學背景。

Luiz:我把自己當成是一名對計算機在數學中扮演什麼角色感興趣的數學家。當然,我非常喜歡計算機。:)

Roberto:即使是那些有正規計算機科學背景的人,也沒有達成共識,我們缺乏一個交流的共同基礎。很多人認為是 Java 建立了螢幕、虛拟機以及接口(相對于類)等。

是不是有很多計算機科學學科僅僅隻是一種職業訓練?

Roberto:是的。而且,很多程式員甚至連計算機科學的學位都沒有。

Luiz:我并不這麼認為,但我不是作為一名程式員被雇用的。從另外一方面來說,我認為,要求程式員有計算機科學學位或是諸如此類的認證是錯誤的。計算機科學學位并不代表很好的程式設計能力。很多優秀的程式員也沒有計算機科學學位(或許這隻在我開始程式設計時成立;現在我可能是太老了)。我的觀點是,一個人擁有計算機科學學位并不能保證他程式寫得好。

Roberto:要求所有的專業人士都擁有學位是不對的。但我的意思是這個領域的“文化”太薄弱。幾乎沒什麼東西需要人們必須知道。當然,雇主可以制定自己的要求,但不應該對學位有嚴格規定。

數學在計算機科學,特别是程式設計方面,起到一個什麼作用?

Luiz:好吧,我是一位數學家。對我來說,數學無處不在。我之是以被程式設計所吸引,很可能是因為它具有數學的特性:精确、抽象和優雅。編寫一個程式有如對一個複雜定理的證明,你可以持續不斷地精煉和改進,而且它還能幹點實際的事情!

當然,我在程式設計時根本沒想這些,不過我認為,數學的學習對于程式設計是非常重要的。它有助于帶你進入一種特定的心境當中。如果你習慣以抽象事物的自身法則去思考問題,程式設計就變得更簡單。

Roberto:按照 Christos H. Papadimitriou 的說法,“計算機科學是新的數學”。一名程式員如果沒有數學功底,就很難有大的作為。從更廣的視野來看,數學和程式設計都具有一些共同的思想原則:抽象。它們都使用同一個關鍵工具:形式邏輯。優秀的程式員任何時候都在使用“數學”,利用它來确立 code invariants 以及接口模型等。

很多程式設計語言都是數學家建立的——或許這就是程式設計困難的原因所在!

Roberto:我會把這個問題留給我們的數學家。

Luiz:好的,此前我已經說過,程式設計絕對具有數學品質:精确、抽象、優雅。對我來說,設計程式設計語言就像是建構一種數學理論:你提供了功能強大的工具,其他人可以使用它來做很出色的工作。我一直被那些規模小而功能強的程式設計語言所吸引。強大的原語和結構之美如同強大的定義和基本理論之美。

你是如何區分出優秀的程式員的呢?

Luiz:你也知道。如今,糟糕的程式員更容易識别——不是因為他們的程式很糟糕(盡管那些程式通常非常複雜又混亂不堪),而是因為你可以感覺到,程式設計對他們來說并不愉悅,好像他們寫的程式對他們自己來說是一個神秘事物,一種負擔。

調試技能如何教授?

Luiz:我認為調試無法教授,至少不能正式地教授。不過當你跟别人,一個或許比你經驗更豐富的人,一起調試的時候,你可以通過具體案例來學習。你可以從他們那裡學習調試政策:如何去縮小問題範圍,如何去做出預測和評估結果,判斷哪些是沒有用的,隻是些噪音而已。

Roberto:調試本質上是在解決問題。它是一個需要來調動你已學會使用的一切工具的活動。當然存在一些實用的技巧(例如,如有可能,盡量不用調試器,在用 C 這樣的底層語言程式設計時,使用記憶體檢查器),不過,這些技巧隻是調試的一小部分。必須像學習程式設計那樣學習調試。

你如何測試和調試你的代碼呢?

Luiz:我主要是一塊一塊的建構,分塊測試。我很少使用調試器。即使用調試器,也隻是調試 C 代碼。我從不用調試器調試 Lua 代碼。對于 Lua 來說,在适當的位置放幾條列印語句通常就可以勝任了。

Roberto:我差不多也是這樣。當我使用調試器時,通常隻是用來查找代碼在哪裡崩潰了。對于 C 代碼,有個像 Valgrind 或者 Purify 這樣的工具是必要的。

源代碼中的注釋起到什麼作用?

Roberto:用處不大。我通常認為,如果有什麼需要注釋的,那隻是因為程式沒寫好。對于我來說,一條注釋更像是打了個便簽,它在說“以後記得重寫這段代碼”。我認為清晰的代碼要比帶注釋的代碼可讀性更強。

Luiz:我同意。我一直堅持:注釋應該用來表達代碼不能清晰表達的東西。

一個項目應該如何文檔化呢?

Roberto:強制執行。沒有什麼工具可以代替一份井井有條、深思熟慮的文檔。

Luiz:但是,為一個項目的發展曆程寫出好的文檔,唯一的可能就是從一開始就把這一點放在心上。Lua 并沒有這樣做;我們從來沒想到 Lua 能發展這麼快,并在今天獲得這麼廣泛的應用。我們在撰寫 HOPL 論文的日子裡(這花了将近兩年時間!),我們發現已經很難記起當時是怎麼做出一些設計決策的了。從另外一個角度來說,如果早期我們要求會議都有正式的會議記錄,可能就會失去一些自發性,并錯失一些樂趣。

在代碼庫的發展曆程中,你需要權衡哪些因素?

Luiz:我會說“實作的簡單性”。這樣做的話,速度和正确性随之而來。同時,靈活性也是重點,這樣,如果需要,你可以換一個實作方式。

可用的硬體資源如何影響程式員的心态?

Luiz:我是個老家夥了。我是在一台 IBM 370 上學習的程式設計。要花上幾個小時來給卡片穿孔、送出給隊列再等到列印輸出。我見過各種各樣的慢機器。我認為程式員應該體驗一下這些機器,因為并不是世界上人人都有最快的機器。編寫給大衆使用的應用程式的人應該在慢機子上試一下,這樣才可以獲得更廣泛的使用者體驗。當然,僅可能用最好的機器來開發:把大量時間花在等待完成編譯上可一點也不有趣。在現在的全球網際網路中,Web 開發者應該嘗試慢速連接配接,而不是他們工作機上的超快連接配接速度。以平均水準的平台為目标,會讓你的産品速度更快、更簡單,而且更好。

就Lua來說,“硬體”是指 C 編譯器。我們在實作 Lua 的過程中學會的一點就是:以可移植性為目标确實值得。幾乎從一開始,我們就是用非常嚴格的ANSI/ISO C (C89) 來實作 Lua 的。這樣一來,Lua 就可以在專用硬體上運作,比如機器人、列印機固件和網絡路由器等,這些沒有一個是我們當初的實際目标平台。

Roberto:你應該始終認為硬體資源有限,這是一條金科玉律。它們當然總是有限的。“自然厭惡真空”;任何程式都有擴充的趨勢,直到它用完了所有的可用資源。此外,随着确定平台上的資源越來越便宜的同時,又會出現一些有嚴格限制的新平台。微型計算機是這樣;行動電話是這樣;一切都是這樣。如果你想做成市場第一,你最好要時刻關注你的程式需要什麼資源。

對于現在或者不久的将來開發計算機系統的人,你在發明、開發和完成你的語言方面,有什麼經驗可以說的嗎?

Luiz:我認為,程式員應該始終記住:并非所有的應用程式都是運作在功能強大的桌上型電腦或者筆記本電腦上的。很多應用程式要運作在受限的裝置上,比如說手機,甚至是更小的裝置等。設計和實作軟體工具的人們應該特别關注這個問題,因為沒有人會告訴你,你的工具會在什麼地方如何使用。是以,就應該為使用最小的資源而設計。你可能會驚奇地發現:很多環境使用了你的工具,而你并沒有把這些環境作為主要的應用目标,你甚至都不知道它們的存在。Lua 就碰到過這種事!而且這很自然;我們内部有一個笑話,這其實不是一個真正的笑話:我們讨論在 Lua 中的一個特性的細節時,我們問自己,“好的,不過它會不會在微波爐上運作呢?”

Lua 易于嵌入,而且要求的資源也非常少。你是如何設計的,使得它适應硬體、記憶體和軟體資源都很有限的情況?

Roberto:開始時,我們并沒有把這些目标搞得很明确。我們隻是為了完成項目才不得已而為之。随着我們的發展,這些目标對我們來說變得更為清晰。現在,我想各方面的主要問題都始終是經濟問題。例如,無論什麼時候,有人建議一些新的特性,第一個問題就是需要多大的成本。

你有沒有因為特性成本太高而拒絕添加它們呢?

Roberto:幾乎所有的特性,相對于它們能帶給語言的東西來說,都“成本太高”。舉一個例子,甚至一個簡單的 continue 語句都不符合我們的标準。

添加一個特性需要帶來多大的收益才是值得的呢?

Roberto:沒有固定的規範,不過看該特性是否能讓我們感到“驚喜”是條好的判斷标準;也就是說,不僅僅滿足其初始其初始動機。這讓我想起了另一條經驗法則:多少使用者會從該特性中受益。某些特性隻對一小部分使用者是有用的,而其他特性對于幾乎所有人都是有用的。

你有例子說明一條新特性對很多人都有用嗎?

Roberto:for 循環。我們甚至反對過這個特性,不過當它出現時,它改變了書中所有的例子! 弱表也是出奇地有用。使用它們的人并不多,不過他們應該試試。

在 1.0 版本之後的多年裡,你都沒有把 for 循環加上。是什麼驅使你不加它?而又是什麼使你最終加入了它?

Roberto:我們曾無法找到一種讓循環通用而簡潔的格式,以至于我們一直不肯加入它。當我們發現可以使用一個生成器函數這樣一個不錯的形式後,我們就把 for 循環加上了。實際上,閉包是使生成器簡單通用的要素。因為把生成器函數做成閉包,可以在循環過程中保留其内部狀态。

更新代碼來擷取新特性的優勢,重新得到更好的程式設計實踐經驗,這些會引起大塊費用嗎?

Roberto:新特性不是必須使用的。

那麼人們會選擇一個 Lua 的版本一直用到整個項目的生命期結束,從不更新嗎?

Roberto:我認為,在遊戲領域大多數人确實是這樣做的。而在其他領域,我認為有一些項目不斷更新他們所用的 Lua 版本。不過有個反例,魔獸世界從 Lua 5.0 更新到了 5.1 !請留意 Lua 現在要比早年的時候穩定多了。

你們在開發過程中是如何分工的,特别是在編寫代碼方面?

Luiz:Lua 第一版是由 Waldemar 在 1993 年編碼的。自 1995 年左右以來,Roberto 編寫和維護了主要代碼。我負責一小部分:位元組碼 dump/undump 子產品和獨立編譯器 luac 。我們一直在修改代碼,并通過電子郵件向其他人發送代碼修改建議,而且,我們就新特性及其實作開了很長時間的會議。

你從使用者那裡得到了很多有關語言和實作的回報嗎?對于在語言中加入使用者回報及其修改,你有一個正式的機制嗎?

Roberto:我們開玩笑說:你要是忘了什麼,那它肯定不重要。Lua 讨論清單非常活躍,不過一些人将開放軟體和社群項目等同視之。有一次,我向 Lua 清單發送了以下消息,總結了我們的方法:

Lua 是一款開放軟體,不過它從未進行過開放式開發。這并不意味着我們沒有聽取其他人的意見。實際上,我們幾乎閱讀了郵件清單中的每一條消息。Lua 裡面的若幹重要特性就起源或發展至外部的貢獻(元表、協程,以及閉包的實作,這裡僅舉出幾個重要的名字),不過,一切都要由我們來最終決定。我們這麼做并非覺得我們的判斷要比其他人的更好。而僅僅是因為我們想讓 Lua 成為我們想要的語言,而不是世界上最流行的語言。

由于采用了這種開發風格,我們不願意為 Lua 建一個公開的代碼倉庫。我們不想會我們做的每一處代碼修改處處解釋。不想為所有的更新保留文檔。我們想在有些奇怪的想法時,有足夠的自由來試一下,不滿意的話就放棄掉,而不需要對每個行動都做一個解釋。

為什麼你喜歡獲得建議和想法,而不是代碼?我在想,或許你自己寫代碼能夠讓你學到關于問題(解決方案)的更多知識。

Roberto:差不多可以這麼說。我們喜歡徹底搞清楚在 Lua 中發生了什麼,是以,一段代碼貢獻不大。一段代碼并不能解釋為什麼采用這種方式,但是,一旦我們了解了它的根本思想,編寫代碼就成了我們不想錯過的樂事。

Luiz:我想對于引入第三方代碼還有一個問題,我們無法確定其所有權。我們肯定不想溺死在要别人把代碼授權給我們的合法化的過程中。

Lua 會不會達到這種狀态:你已經添加了所有想要添加的特性,唯一需要的就是改進實作(例如,LuaJIT)?

Roberto: 我覺得現在就處于這種狀态。我們已經添加的特性,即使不算是全部,也是我們想要添加的絕大部分。

你是如何操作冒煙測試和回歸測試的?使用開放代碼倉庫的一大好處是,你可以讓人們對幾乎每一個修改進行自動測試。

Luiz:Lua 的釋出并沒有那麼頻繁,是以,釋出一個版本時,已經進行過很多的測試。當這個版本已經相當可靠時我們才釋出工作期版本 ( work version / pre-alpha 版),人們能夠看中看到新添加的特性。

Roberto:我們确實進行了嚴格的回歸測試。重點在于:因為我們的代碼是用 ANSI C 編寫的,基本上沒有什麼可移植性問題。我們沒有必要在若幹不同的機器上進行測試。一旦修改了代碼,我就會執行所有的回歸測試,不過這一切都是自動進行的。我要做的隻是敲一下 test all 。

如果發現了一個反複出現的問題,到底是局部臨時解決,還是全局通盤考慮,你如何判斷哪一種是最佳解決方案?

Luiz:我們一直盡量做到一發現 bug 就修複它。不過,因為我們并不經常釋出新的 Lua 版本。是以我們都是等到有足夠的修複量才釋出一個小版本。大版本做的都是改進工作而不是修複 bug 。 如果問題非常複雜(這種情況很罕見),我們會提供一個小版本作臨時解決方案。而在下一個大版本中通盤考慮來解決它。

Roberto:通常,局部的權宜修複很快就可以完成。隻有在确實不可能進行全局修複時,我們才會作局部的權宜方案。例如,如果某個全局修改需要一個新的不相容接口。

從開始到現在,已經過去了這麼多年,你仍然會為有限的資源而設計嗎?

Roberto:當然會的,我們一直緻力于此。我們甚至考慮過改變 C 結構内的字段順序,以節省幾個位元組。:)

Luiz:相比于以前,現在有更多的人們把 Lua 語言運用到比以前更小的裝置上面。

以使用者視野來對簡單性的追求怎樣影響語言設計的?我想起了 Lua 對類的支援,讓我想起了許多在 C 中實作面向對象的方式(不過沒那麼另人煩惱)。

Roberto:目前,我們有一個準則叫“機制而非法策”。它可以保證語言簡潔,不過就像你說的,使用者必須提供它自己的法則。就類這個問題來說,有很多方法實作它。有些使用者會喜歡某種方式,而其他使用者則可能痛恨它。

Luiz:這個确實賦予了 Lua 一種 DIY 的風格。

Tcl 也用了一種類似的方法,不過各家各有其法使它支離破碎。因為 Lua 有特定的目的,是以分裂對它不是啥嚴重問題嗎?

Roberto: 對。有時這是個問題。但對于大量應用(比如說遊戲)來說,這不是個問題。Lua 主要用來嵌入到别的應用程式中。而應用程式會提供一個堅固的架構來統一程式設計規範。你看到了 Lua/Lightroom, Lua/WoW, Lua/Wireshark —— 這個每個都有自己的内部文化。

你認為 Lua 這種“我們提供機制” 的展延性風格,給人帶來巨大的好處嗎?

Roberto:這麼說并不确切。對于大多數事情來說,它是一種折衷處理。有時候,提供即刻可用的規範法則非常有用。“我們提供機制”更為靈活,但需要做更多的工作,并使得風格分裂。這最終也是個經濟問題。

Luiz:另一方面,有時候這很難向使用者解釋。我的意思是,讓他們了解是這些機制是什麼,以及這些機制的原理。

這會使項目之間交流代碼變得困難嗎?

Roberto:沒錯,通常就是這樣。它也阻礙了獨立庫的發展。例如,WoW 擁有大量的庫(甚至連用遺傳算法解決貨郎擔問題的庫都有),不過在 WoW 之外卻沒人去用它們。

你擔心 Lua 會是以分裂成 WoW/Lua,Lightroom/Lua 等分支嗎?

Luiz:我們并不擔心:語言還保持相同,隻是可用的函數不同而已。我認為這些應用程式會在某些方面受益于此。

嚴肅的 Lua 使用者會在 Lua 基礎上編寫他們自己的方言嗎?

Roberto:很有可能。至少我們還沒有宏。要是有宏的話,我認為你可以使用宏來建立一種真正的方言。

Luiz: 本質上還不算一種語言的方言。不過算是用函數來實作的一種特定領域語言。這曾是 Lua 的設計目的之一。當 Lua 僅僅用來作資料檔案時,它看起來是一種方言,當然那些隻是 Lua 表而已。有些項目或多或少實作了一些宏。比如我想起了 metalua 。這也是 Lisp 的一個問題。

你為何選擇提供一種可擴充的語義?

Roberto:它開始是作為提供面向對象特性的一個方法。我們不想在 Lua 中添加 OO 機制, 但使用者想要這些。我們想到這個方法,提供足夠的機制讓使用者實作自己的 OO 機制。到現在我們也覺得這是一個正确的決策。然而,這使得用 Lua 的方式 OO 程式設計對于初學者來說更為困難。但它也給語言帶來了大量的靈活度。特别是,當我們把 Lua 和其它語言混用(這是 Lua 的一個特色)時,這種靈活度使得程式員可以讓 Lua 的對象模型去适應外部語言的對象模型。

目前的硬體、軟體、服務和網絡環境同你最初設計時的系統環境有何不同?這些變化對你的系統以及未來的改變有何影響?

Roberto:因為 Lua 是以極高的可移植性為目标,我認為目前的“環境”同以前的環境并沒有什麼不同。例如,我們開始開發 Lua 時,DOS/Windows 3 跑在 16 位機器上;一些老機器仍然是 8 位的。目前我們沒有 16 位的桌上型電腦了,不過,若幹使用 Lua 的平台(嵌入式系統)仍然是 16 位或者甚至是8位的。

最大的變化在于 C 語言。回頭看 1993 年,當時我們剛開始做 Lua ,ISO (ANSI) C 還沒有像今天這麼成熟。很多平台仍然使用 K&R C 。很多應用程式寫了一些很複雜的宏來使得程式通過 K&R C 和 ANSI C 兩者的編譯。主要的差別在函數頭的聲明。當時,堅持使用 ANSI C 是一個冒險的決定。

Luiz:我們仍未感覺到有必要轉移到 C99 上面。Lua 是用 C89 實作的。如果過渡到 64 位機器上時出現些小毛病的話,或許我們必須使用 C99 的一部分(特别跟長度有關的類型定義),不過我并不希望出現任何問題。

如果能全部重新建構 Lua 的 VM 的話,你仍然會堅持使用 ANSI C 嗎,或者你希望有一個更好的語言用于跨平台的底層開發?

Roberto:不。ANSI C 是我(目前)知道的可移植性最好的語言。

Luiz:有些傑出的ANSI C編譯器,不過,即使是使用它們的擴充,也不會給我們帶來很多性能提升。

Roberto:改進 ANSI C 并保持它的可移植性和性能并不容易。

順便問一句,你是說 C89/90 嗎?

Roberto:是的。C99 尚未确認好。

Luiz:再者,我不确定 C99 能給我們帶來很多額外的特性。我還特别想到了 gcc 中使用的帶标簽的 goto 語句作為 switch 的一種替代方案(在虛拟機執行的主幹裡)。

Roberto:在很多機器中,這樣做可以改進性能。

Luiz:我們早期對它作過測試,最近也有人也對它進行了測試,效果并不吸引人。

Roberto:部分原因在于我們基于寄存器的體系結構。它傾向于用較少的操作碼,每個操作碼分擔更多的工作。這減少了分發器的負擔。

你為什麼要建構一個基于寄存器的 VM 呢?

Roberto:為了避免所有的 getlocal/setlocal 指令。我們也想去實踐一下我們的想法。我們想啊,如果它運作得不好,至少我們還能寫一些研究這個的論文。而最後,它運作得非常好,而我們也隻寫了一篇論文。:D

在 VM 上運作對調試有沒有幫助?

Roberto:它沒有提供“幫助”;它改變了整個調試的概念。既調試過編譯型語言,又調試過解釋型語言(比如 C 和 Java)的人都知道它們天差地别。好的VM 會讓語言變得更安全,在某種意義上,該錯誤可以從語言層面上了解,而非機器層面(比如說段錯誤)。

如果語言是平台無關的,這對調試有何影響?

Roberto:通常它有利于調試,因為一種語言越是和平台無關,它就越需要可靠的抽象描述和行為。

考慮到我們是人,而人總會犯錯。你是否曾經考慮過:為了在調試階段有所幫助,需要向語言添加某種特性或是從中删除一些特性?

Roberto:當然了。輔助調試的第一步就是良好的錯誤消息。

Luiz:從初期版本開始,Lua 中的錯誤消息就在一直改進。我們已經從可怕的“調用操作對象不是一個函數”的錯誤消息(這條錯誤消息一直用到 Lua 3.2),變成了更好的錯誤消息:“試圖調用全局 'f' (一個 nil 值)”。從 Lua 5.0 開始,我們使用對位元組碼的符号追蹤 (Symbolic execution) 來試着提供更有用的錯誤消息。

Roberto:在語言自身的設計中,我們一直設法避免使用複雜的結構。如果它很難了解,就會更難調試。

在設計一門語言和設計用這種語言編寫的程式之間,有什麼聯系?

Roberto:至少對我來說,設計一門語言的要點在于從使用者的角度出發,也就是說,去考慮使用者将怎樣使用每一個特性,使用者将會如何将這些特性和其它語言對比。程式員總會找到使用一種語言的新方式,優秀的語言應該允許那些意想不到的使用方法。不過,語言的“正常”用法應該遵從語言設計者的初衷。

語言的實作會在多大程度上影響語言的設計?

Roberto:這是一條雙向道。實作會對語言産生巨大的影響:我們不應該設計無法高效實作的東西。一些人忘了這點。在設計任何軟體時,效率一直是一個(或者是惟一的)主要限制條件。不過,設計也可能會對實作産生較大的影響。一眼看去,Lua 的幾個特色之處都來自于它的實作(體積小、優秀的 C API ,以及可移植性),而 Lua 的設計在使這些實作變得可能中,起到了關鍵作用。

我讀過你的一篇論文,《 Lua uses a handwritten scanner and a handwritten recursive descent parser( Lua 使用一個手寫掃描程式和一個手寫的遞歸下降分析器)》。你是如何開始考慮手工建構一個分析器的?是不是從一開始就很清楚,這樣做要比 yacc 生成的分析器要好?

Roberto:Lua 第一版使用了 lex 和 yacc 。不過,Lua 最初的主要目标之一是作為一種資料描述語言,和 XML 沒什麼不同。

Luiz:但是時間要更早一些。

Roberto:很快人們開始把 Lua 用于數兆位元組的資料檔案,此時 lex 生成的掃描器迅速變成了瓶頸。手寫一個優秀的掃描器非常容易。而且隻做了這麼一點簡單的改進後,我們就提高了 Lua 大約 30% 的性能。

決定從 yacc 改成手工編寫解析器是很後來的事情,這個決定做得并不容易。這起源于幾乎所有 yacc/bison 實作使用的主幹代碼的問題。

當時,它們的可移植性很差(例如,用了好多處的 malloc.h ,這是一個非 ANSI C 的頭檔案),而且,我們無法控制其整體品質(例如,控制堆棧溢出和記憶體配置設定錯誤等問題),而且它們也不是可重入的(比如要在解析代碼的過程中調用解析器)。另一方面,如果你想要像 Lua 那樣及時生成代碼,自底向上解析器也不如自頂向下的那麼好。因為它難以處理“繼承屬性(Inherited attributes)”。我們改寫之後,發現我們手寫的解析器要比 yacc 生成的那個略小以及略快一點。不過這不是改寫的主要原因。

Luiz:自頂向下分析器還能提供更好的錯誤消息。

Roberto:不過,我從不推薦為沒有成熟文法的語言手寫解析器。并可以肯定LR(1)(或是 LALR 甚至 SRL)會比 LL(1) 強大多了。甚至對于 Lua 這樣的簡單文法的語言來說,我們也必須使用一些技巧來建構一個像樣的分析器。例如,處理二進制表達式的程式并沒有按原始文法去處理,而是用了一個聰明的基于優先級(priority-based)的遞歸方案。在我的編譯器課上一直向我的學生推薦 yacc 。

你的教學生涯中有什麼趣聞轶事嗎?

Roberto:我剛開始教授程式設計時,供我們的學生使用的計算機裝置是一台大型機。有一次,一個非常優秀的團隊送出的一個程式作業,居然連編譯都沒通過。我找他們來談話,他們發誓用好幾個測試案例仔細的測試了程式。當然了,他們和我用的是同一台機器,完全相同的環境,都是在那台大型機上。這個神秘事件隻到幾周後才搞明白。原來機器上的 Pascal 編譯器被更新了。更新剛好發生在學生完成任務和我開始批改作業之間。他們的程式有一個很小的詞法錯誤(如果記得沒錯,是多了個分号),而老的編譯器沒有檢測到!

繼續閱讀