1. 程式人生 > >Lua: 好的, 壞的, 和坑爹的

Lua: 好的, 壞的, 和坑爹的

應用 res one box 語法 org 經歷 工作 數字類型

Lua: 好的, 壞的, 和坑爹的

來源: http://blog.csdn.net/xoyojank/article/details/12762909

在我使用Lua編程整整9個月後, 是時候停下來反省一下這段經歷了. 過去了幾年裏, 我使用了各式各樣的語言:Perl (soaplite.com, 還有其它的項目, 包括我現在的咨詢工作), C (DHCPLite 和 ping-pong juggling robot), JavaScript (Google Maps相關經驗 和 canvas), MATLAB (ping-pong juggling robot), 等等, 從Turbo Pascal 到 F# -- 比較一下Lua和我接觸過的其它語言是一件非常好玩的事情. 我已經使用Lua完成了一些不同類型的項目: 一個遠程調試器(MobDebug), 擴展了一個LuaIDE (ZeroBrane Studio), 一個移動應用程序 (LuaRemote), 一些教育性的腳本 (EduPack), 還有一個使用Lua在瀏覽器畫板上進行繪圖的demo.

雖然我已經見過很多提到Lua的好和壞的列表 (例如, Lua的優勢, 為什麽使用Lua, 為什麽Lua沒有得到廣泛地應用, Lua的優點,Lua的好和壞, Lua對比JavaScript, 還有Lua的陷阱), 但是有些特性坑死爹了, 還有些他們忘了提, 所以我就自己搞了個列表. 雖然這說不上非常專業, 也沒有覆蓋到語言的每個方面 (如 math 和string 庫), 但這是根據我的編程語言經歷得出來的.

好的

  • 小巧: 20000行C代碼 可以編譯進182K的可執行文件 (Linux下).
  • 可移植: 只要是有ANSI C 編譯器的平臺都可以編譯. 你可以看到它可以在幾乎所有的平臺上運行:從 microcontrollers
    Lego Minstorms NXT, 到 移動平臺, 到 遊戲主機,甚至 瀏覽器 (翻譯成JavaScript).
  • 作為一個嵌入式可擴展語言 提供了簡單直接的 C/C++交互接口.
  • 足夠快:見 與其它語言的性能比較, 還有一個 JIT編譯器可以顯著地提高多數任務的性能; 對於那些仍然對性能不滿意的人, 可以把關鍵部分使用C實現, 然後與其集成, 這樣還可以享受其它方面的好處. [3/9/2013更新]替換已經消失的結果為 benchmarksgame.
  • 文檔完善: 參考手冊, 書籍, wiki, 6頁的簡短參考 等.
  • 友好和熱情的社區. 在傑出的文檔, wiki, 郵件列表, 和 StackOverflow中,沒有什麽問題沒有我找不到答案的.
  • 適合初學者和非程序員的簡潔語法. Lua 從 Modula (Pascal的分支, 已經廣泛應用於教育做為教學語言)借鑒了多數的控制語法. 我現在仍然記得早期使用過Philippe Kahn的快速而優雅的 Turbo Pascal IDE.
  • 集成的解釋器:只需要在命令行下運行 lua.
  • 先天的協程支持, 用於實現 叠代器 和非搶占式多線程.
  • 低延遲的增量垃圾回收, 沒有額外的內存開銷, 低實現復雜度, 並且支持 weak tables.
  • 強大並多樣化的表 可以保存任意類型的數據 (除了 nil) , 還可以使用任意類型的值進行索引 (除了 nil): {1, 2, 5, foo = "bar", [func] = "something", ["some spaces"] = value()}.
  • 詞法作用域.
  • 一流的函數 和 閉包 支持的 函數式編程.
  • 尾調用: return functioncall().
  • 遞歸函數不需要事先聲明: local function foo() ... foo() ... end; 註意這樣不行 local foo = function() ... foo() ... end.
  • 函數返回 多個值: return 1, 2, 3. 調用者可以認為返回值是任意個數的: 如果多於3個, 其余會被丟棄; 如果少於3個, 那其它的會是未初始化的 nil.
  • 函數允許變化的變量個數, function foo(...) local args = {...}; bar(param, ...) end.
  • Table可以 "拆包" 成參數列表,unpack (或 Lua 5.2的 table.unpack): print(unpack({1, 2, 3})) 打印1 2 3.
  • 操作環境變量 (Lua 5.1中的getfenvsetfenvLua 5.2中的_ENV 操作), 此外還可以構造 沙盒 .
  • 同時賦值多個變量: local a, b, c = 1, 2, x, y = y, x, or a, b = foo().
  • 多行字符串 (using [[...]]; 可以使用 [[...[=[...]=]...]])包含和註釋 (--[[...]]).
  • 可選的分號語句分隔符 (多數用於解決模棱兩可的的情況 a = f; (g).x(a)).
  • 重載使用 metatables.
  • 元編程 可以根據你的 DSL修改抽象語法樹來創造新的語法.
  • for 語句有兩種形式: generic (使用叠代器: for a in iter() do ... end) 和 numeric (使用數字: for a = 1, 5, 0.1 do ... end); 數字的這個支持各種類型的步進 (不僅僅是整數).
  • 函數調用的語法糖 (f‘string‘, f"string", f[[string]], andf{table})和方法調用(obj:m()).
  • 簡單而強大的 調試 庫.

與眾不同的

  • 表和字符串索引從1而不是0開始.
  • 對一個表中的值賦 nil 會從表中刪除它. 這就是說對於不存在的值返回 nil , 所以元素存不存在跟它是不是 nil是同一個問題. a = {b = nil} 產生一個空表.
  • 沒有獨立的整數類型; 數字類型 表示的是實數.
  • 沒有類; 面向對象 使用 表 和 函數實現; 繼承使用 metatable 機制實現.
  • 方法調用使用 object:method(args) 的寫法, 與 object.method(object, args) 的寫法是等價的, 但 object 只取值一次.
  • nilfalse 是僅有的表示假的值; 0, 0.0, "0" 等其它的一切值都是true.
  • 不等於是 ~= (例如, if a ~= 1 then ... end).
  • not, or, and 操作符是邏輯運算符.
  • 賦值是語句, 這就意味著沒有 a=b=1if (a=1) then ... end的寫法.
  • 沒有 a+=1, a++, 或其它簡寫形式.
  • 沒有 continue 語句, 盡管有一個 解釋 和一堆的替代品, 如在循環中使用 repeat break until true 跳出 或者使用一個Lua 5.2中的goto 語句.
  • 沒有 switch 語句.
  • 某些上下文可能會用到括號; 例如, a = {}; a.field 正常, 但{}.field 不行; 後者需要這樣寫 ({}).field.
  • 循環的控制變量默認是局部的, 循環完了就沒了.
  • for 循環中的極限和步進值是 緩存過的; 這意味著 for i = init(), limit(), step() do ... end 中的三個函數 init, limit, 和step 只在循環開前調用過一次.
  • 條件 和其它控制語言不需要括號.
  • 字符串和數字會自動轉換 (需要一個數字時提供一個字符串, 反之亦然), 除了相等比較: 0 == "0"false, {} ~= 1true, 還有foo["0"]foo[0] 引用的是表中不同的值; 其它關系運算符會在比較不同類型的值時產生錯誤.
  • 逗號和分號 都可以作為表中的元素分隔符; 也同樣都可以作為 可選的分隔符 放在結束括號前: a = {a = 1, b = 2, }.
  • 比想像中還要少的內部組件; 可能一些人覺得這就像 "電池沒有包含在內"一樣. 從另一個角度來看, 這成就了它的緊湊而又可移植的核心, 不過同時有一些庫可以進行補償, 如 LuaRocks 和Penlight.

壞的

  • 有限的錯誤處理支持 (使用pcall 和xpcall),盡管有些人 爭論這已經夠用了 , 只需要加一些語法糖和特性支持 (如確定性的finalizer). pcallerror 的組合十分強大, 特別是 error 可以返回任何東西 (例如一個表)而不是僅僅是一個字符串, 但是 catch ... finally 結構在多數情況下可能更加清晰直觀.
  • 默認是全局的作用域 (這麽說對 Lua 5.2不公平, 它已經沒有全局了). 有一個 strict 模塊要求所有全局變量都需要初始化. 雖然我並沒有很多問題是由未初始化的全局變量引起的, 但還是把它放到"壞的"分類, 因為有一次我犯了一個錯誤, 在調用一個"next"變量時沒有局部化它, 引起一個問題 就是叠代器覆蓋了另一個模塊的next 函數.
  • 沒有Unicode 支持 (最起碼string.len 和模式識別函數需要識別 Unicode 字符); 不過有一個ICU庫的 綁定 實現了Unicode支持. 可以看一下這條 消息 和後續總結的的關於現有的支持和string.* 需要什麽樣的修改.
  • 有限的模式匹配支持, 盡管已有的也十分強大.在使用了15 年 Perl後, 我非常想念其中的一些正則表達式特性(多數是前向搜索, 可選組 (group )?, 還有組內組), 沒有任何一個都是會增加實現復雜度的. 對於需要更強大的正則表達式的人可以使用 LPeg 和它的 re 模塊.
  • 沒有三目運算符; 有一些替代品. 一般我使用 foo = test and value1 or value2 形式, value2testvalue1 都為 false時可以賦值.
  • 沒有內置POSIX函數. 雖然有 luaposix 模塊, 但是它需要編譯, 這並不是一個好的選擇. 盡管對於這個我並沒有很強的需求, 但是每當我需要獲取/設置一個環境變量時總會直觀想到去訪問 getenvsetenv [6/1/2012更新] miko 在評論中提到, 有 os.getenv, 但是沒有相應的 os.setenv.
  • 沒有類/對象 finalizer. Lua 通過 __gc metamethod提供finalizer 的功能 , 但它只能用於自定義類型 (不是表), 並且不能跟其它語言的相應功能匹配, 舉例來說, Perl中的 DESTROY 和 END方法. [05/27/2012更新] Lua 5.1中有一個沒有文檔說明的 newproxy特性, 它實現了表的 finalizers; Lua 5.2 移除了這個特性的同時增加了 表的__gc元方法.
  • 沒有Lua和C代碼之間的yielding: coroutine.yield 在跨越 Lua/C 邊界調用時會失敗 attempt to yield across metamethod/C-call boundary. 我在使用 luasocket和協程進行異步編輯時多次遇到過這個錯誤, 最後使用 copas 模塊解決. 在Lua 5.2中這個問題得到解決.

坑爹的

  • 表中元素的個數並不是很容易獲取, 結果取決於你怎麽做 (或你怎麽定義"長度"). 這可能不是個意外, 因為Lua提供了強大的表並支持靈活的索引方式 (數字或其它Lua類型, 除了 nil). Lua中的表有兩部分: "數組" 部分(使用 t = {1, 2, 3}生成) 和 "哈希" 部分(使用t = {a = "foo", ["b"] = 2}生成); 這兩者可以靈活地結合在一起. #table 返回最短的"數組"部分長度(沒有任何缺口) 而table.maxn(t) 返回最長的 "數組" 部分(Lua 5.2移除了這個函數). "哈希" 部分沒有定義長度. 兩者都可以使用 pairs 方法進行遍歷, 同時允許你對其中的元素進行計數. 然而, print(#{1, 2, nil, 3}) 打印4 卻不是想像中的 2 ,print(#{1, 2, nil, 3, nil}) 打印的則是2. 我確信有一個合理的理由解釋它, 但是現在說是就是"坑爹"的地方. [11/17/2012更新] FireFly 在評論中提到, Lua 5.2 中表的長度 只定義成 沒有洞的.
  • return 必須是語句塊中的最後一句; 也就是說,function foo() print(1); return; print(2) end 會觸發一個錯誤 ‘end‘ expected...unexpected symbol near <whatever statement you have after ‘return‘> (這取決於在return 之後有沒有分號). 沒有人會這樣寫, 除非你在調試, 但我卻被它坑了好幾次. 原本我想把它放進"與眾不同的"分類, 但是我發現它前後矛盾. 在一個不能使用 return的地方卻能使用 do return end . [5/19/2012更新] 這同樣出現在 break 語句上, 雖然在Lua 5.2中 break 不再必須是語句塊的最後一句了.
  • 函數只返回一個值但它並不是列表中的最後一個; 如:
      function f123() return 1, 2, 3 end
      function f456() return 4, 5, 6 end
      print(f123(), f456()) -- prints 1, 4, 5, 6
      print(f456(), f123()) -- prints 4, 1, 2, 3
    這個 return 的行為也受到這條規則約束: return f456() 返回3個值, 但return (f456()) 只返回一個值 (註意多出的括號). 關於這個語言特性有 很好的文檔, 但我仍然認為它太坑爹了 (或許在旁人看來它是優點).

總的來說, 到目前為止我很享受這個語言帶來的簡潔和便利, 盡管有些東西跟我之前的做法有點不一樣. 特別是在8歲的兒子很快地學會了Lua的語法後, 我覺得自己的那些關於 Turbo Pascal 的經歷已經過時了.

Posted by Paul Kulchenko on Sunday, March 25, 2012 at 5:22 PM

Lua: 好的, 壞的, 和坑爹的