Lua: 好的, 壞的, 和坑爹的
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
- 作為一個嵌入式可擴展語言 提供了簡單直接的 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中的getfenv
和setfenv
和Lua 5.2中的_ENV
操作), 此外還可以構造 沙盒 . - 同時賦值多個變量:
local a, b, c = 1, 2
,x, y = y, x
, ora, 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
只取值一次. nil
和false
是僅有的表示假的值; 0, 0.0, "0" 等其它的一切值都是true
.- 不等於是 ~= (例如,
if a ~= 1 then ... end
). not, or, and
操作符是邏輯運算符.- 賦值是語句, 這就意味著沒有
a=b=1
或if (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
,{} ~= 1
為true
, 還有foo["0"]
和foo[0]
引用的是表中不同的值; 其它關系運算符會在比較不同類型的值時產生錯誤. - 逗號和分號 都可以作為表中的元素分隔符; 也同樣都可以作為 可選的分隔符 放在結束括號前:
a = {a = 1, b = 2, }
. - 比想像中還要少的內部組件; 可能一些人覺得這就像 "電池沒有包含在內"一樣. 從另一個角度來看, 這成就了它的緊湊而又可移植的核心, 不過同時有一些庫可以進行補償, 如 LuaRocks 和Penlight.
壞的
- 有限的錯誤處理支持 (使用pcall 和xpcall),盡管有些人 爭論這已經夠用了 , 只需要加一些語法糖和特性支持 (如確定性的finalizer).
pcall
和error
的組合十分強大, 特別是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
形式,value2
在test
和value1
都為false
時可以賦值. - 沒有內置POSIX函數. 雖然有 luaposix 模塊, 但是它需要編譯, 這並不是一個好的選擇. 盡管對於這個我並沒有很強的需求, 但是每當我需要獲取/設置一個環境變量時總會直觀想到去訪問
getenv
和setenv
[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: 好的, 壞的, 和坑爹的