來源 https://blog.codingnow.com/2010/06/masterminds_of_programming_7_lua.htmlhtml
《Masterminds of Programming: Conversations with the Creators of Major Programming Languages》是本至關不錯的書。博文翻譯出版了這本書,中文名叫作《編程之魂》。程序員
書是好書,惋惜翻譯這本書須要對各類語言的深刻研究,看起來譯者有點力不從心。出版社打算從新作這本書。受編輯所託,我校對了其中第七章:有關 Lua 的一段。原文讀下來拍案叫好。惋惜譯文許多地方看起來有些詞不達意。許多在口語化交流中提到的術語被忽略了作了錯誤的翻譯。有些部分應該是對 lua 理解不夠而沒能表達清楚。算法
仔細校對了兩段後,我乾脆放棄原譯本,本身動手翻譯了一份(保留了不到 1/4 原來的譯文)。雖然我的能力有限,但也算是每句話本身都看明白了再譯的。雖然說有些地方沒有直譯,但也算沒有夾帶私貨。編程
這裏貼出一段,但願你們閱讀愉快。數組
7. Lua
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。安全
7.1 腳本的威力
你是如何定義 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)。
7.2 經驗
對於你作的這些,你如何定義成功?
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 中的一個特性的細節時,咱們問本身,「好的,不過它會不會在微波爐上運行呢?」
7.3 語言設計
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 編譯器被升級了。升級恰好發生在學生完成任務和我開始批改做業之間。他們的程序有一個很小的詞法錯誤(若是記得沒錯,是多了個分號),而老的編譯器沒有檢測到!