標籤:

Lua 語言有哪些不足?


Lua: Good, bad, and ugly parts: http://notebook.kulchenko.com/programming/lua-good-different-bad-and-ugly-parts

這篇總結的挺全面了。

個人而言,目前LuaJIT和Lua5.2的分裂狀態讓人比較頭疼。


Lua的優點是夠小夠簡潔。但缺點也就是這五個字。對我來說,沒有內建UTF-8支持的語言在我的工作環境中非常不便。雖然可以自己寫庫,但引入了額外的工作。

另一個問題是它的C介面風格。相對於Python和Perl 使用的介面,Lua的介面風格和彙編更接近。開始寫起來時多少有些彆扭。好在時間長了就慢慢習慣了。


我來說說語言特性相關的:

  1. 只有一種複合數據結構table,實際上dict和array是應該分開的,用Lua做過實際項目,應該能明白這是種不小的困擾
  2. 沒有continue,白白增加了一層循環,增加了縮進層次,費電且看起來不方便
  3. 只有一種數字類型(double),這不僅僅有性能問題,語義也不對
  4. 不支持異常處理,默認的方式對編程不友好,不管如何強調保持語言的精簡,也不應該不支持異常處理
  5. 不支持decorator,這是非常重要的語法糖

先寫這麼多,另外談談對上面一些朋友的看法的看法:

  1. 沒有多核(線程)支持,這個問題基本上不成立,多線程支持不見得非得通過語言提供,尤其是對Lua這種以嵌入為基本目標的語言,Lua要保持純Ansi C實現,就不能支持多線程,因為多線程不在C標準中。這裡面值得討論的是如何在Lua中提供多線程支持,第一種是在宿主程序中提供,第二種是作為庫提供,具體的問題談起來比較複雜,不過,如果你需要一個和本地線程N-N的,能好好的按照你的預期工作的平凡的多線程系統,參考《Lua程序設計》第二版,某章就用pthread實現了這麼一個東西,你只需照抄代碼就可以取得。如果使用LuaJIT,可以用LuaJIT/FFI對LuaJIT編程,可以實現一個沒有外部依賴的多線程系統。
  2. C/API難用,至少Lua的C/API和Python的比屬於非常非常好用的,可以說Lua是大體上最容易編程的解釋器/虛擬機


最近讀了 Lua 5.1 源碼,列出一些不太好的設計:

  • lua_isnumber 對於可以轉化為數字的字元串返回 true

  • lua_tostring lua_tolstring 函數會將數字轉化為字元串,而不僅僅是返回字元串值。可測試下面的代碼:

    lua_pushnumber(L, 3);
    lua_tostring(L, -1);
    printf("%s
    ", lua_typename(L, lua_type(L, -1))); -- string

  • lua_objlen 同樣會將數字轉化為字元串,並返迴轉化後的字元串的長度。可以測試下面的代碼:

    lua_pushnumber(L, 3);
    printf("%d
    ", lua_objlen(L, -1)); -- 1
    printf("%s
    ", lua_typename(L, lua_type(L, -1))); -- string

  • 如果 table 中的所有元素都哈希衝突,那麼 next 函數的複雜度為 O(n),遍歷表的時間複雜度為O(n^2)
  • 千萬不要試圖用 "#" 操作符求一個存在 hole 的數組的大小,可以測試這段代碼 :

    print(#{1, nil, 3, nil}, #{1, nil, 3}) -- 1 3

    具體為什麼會這樣,只有讀了 Lua 源碼才能理解。只需要記住求數組大小的前提條件是數組中不存在 hole

  • 字元串哈希演算法對於長字元串會產生漏洞,這點在雲風的 readinglua 中也有提及(在 Lua 5.2 中得到修復)
  • package 模塊不是以絕對路徑作為 key。測試代碼:

    package.path = package.path .. ";lib/?.lua" -- "lib.test" 和 "test" 指代同一文件
    local test1 = require "lib.test"
    assert(g_test == 1) -- 假設在 lib/test.lua 中將這個全局變數初始化為 1
    g_test = 2

    local test2 = require "test" -- 和 lib.test 引用同一個文件
    assert(g_test == 1) -- 由於重新載入 lib/test.lua, g_test 被重新賦值為 1

    assert(test1 ~= test2) -- 同一個模塊會有兩份拷貝

  • Lua 版本升級向來是不兼容的,明明 5.1 有很多不好的地方,但是要想升級到 5.2 卻要做很多工作


其它答案里那些問題,我覺得都不是問題。挺多算 cost 或者 price。

比如說缺乏標準庫,其實在我看來反而是優勢:

  1. 如果有標準庫,體積就會龐大。
  2. C API 就會事實上幾乎變成標準庫維護者的私有 API。目前這種測試和維護良好的狀態可能會改變。

曾經有人說過:在一個 team 里,我可以是唯一用 Eclipse 的,但我不能是唯一用 Python 的。Lua 恰恰改變了這個局面。你可以是唯一用 Lua 的。而 Lua 改變這個局面的原因很大一部分在於它「缺乏」標準庫。

@陳甫鵃 提到的介面太接近彙編,其實這是一個優點。如果不用 explicit-stack 這樣的介面,那麼 C 介面必然臃腫無比。看看 CORBA 的失敗和後來的 JSON 等介面的興起,就可以知道,跨語言介面最高能容忍的抽象程度也就是如此了。而且 explicit-stack 從規則上避免了內存管理的 bug。像 JNI 那樣既需要通知 VM 目前的 ref 狀態,又可以直接通過一個 C 指針來訪問 Java 對象是很糟糕的設計。

不能支持 Unicode。其實在一個成熟的開發環境中,很大的可能是已經有了一個 native 的 Unicode 方案。如果不是讓 script 語言直接和這個方案介面,不僅在實現上產生冗餘,而且 script 語言本身的 Unicode 功能可能還會不足。(當然,Lua 據說要加入 Unicode,但是例如排序之類的功能如何處理還有待觀察)

我認為唯一的缺點是語法太照顧非程序員。不如直接就用 Lisp 的語法好了。Lisp 的語法,加上 Lua 的 C API 和完全線程安全的 VM,就無敵了。不過沒有前者的情況下,我還是義無反顧的選擇後兩者。


目前覺得最大的問題是,語言自帶的庫比較少,多需要第三方提供.當然這樣也保證了這門語言足夠的小.

另外,居然沒有continue關鍵字,導致一些循環語句不好寫.

lua的設計還有一點很奇怪,在一個block中的變數,如果之前沒有定義過,那麼認為它是一個全局變數,而不是這個block的局部變數。這一點和別的語言不同。容易造成不小心覆蓋了全局同名變數的錯誤。具體實現見:lparse.c中的singlevaraux函數第一個if判斷。

前面的朋友提到的介面類似彙編風格也是不太舒服的一個點,這個介面風格需要你對lua棧有相當的了解才能寫出正確的代碼來。

(未完待續,我根據以後的體會慢慢補充吧)


我認為分 壞 的地方以及 不夠方便 的地方兩類來說。前者指通過包裝、封裝及LuaJIT不能解決的問題

壞的地方:

1、不支持多線程。理論上應用JIT技術在技術上可以解決,但目前LuaJIT也未解決(也未嘗試解決)此問題。我認為這是硬傷中的硬傷,導致我不得不在某些場合放棄Lua改用C/C++來編寫代碼。但就我個人而言,我認為這在服務端應用中不是問題。

2、不支持continue

3、在許多情況下不能區分 nil和none,在少數情況下又不得不區分。順帶一提,語言本身里又缺少none這個概念。

不夠方便的地方:

1、缺條件選擇語句。

2、對空洞數組、空洞參數的支持導致少數需求無法完成

3、不能原生支持Unicode(這個是Lua用戶必吐槽的話題了……)基本上用Lua搞項目的都得考慮進行一些侵入或非侵入的改動。

4、不支持原生的面向對象。簡單使用meta-programming實現的面向對象具備良好的動態性但性能略有損失。

後續有空繼續來補充


加一點:nil 值很坑爹。它不能被存到表裡,作為參數傳遞時也可能丟失(Lua5.1 和 Luajit 的行為還不一樣)。你能寫出一個判斷其參數中是否有 nil 值的函數嗎?


for cond1 do
if !cond2 then
continue
end
do()
end

for cond1 do
if cond2 then
do()
end
end

上面兩段代碼等價。結構化編程告訴我們,不要亂跳,不要亂用否定。沒有continue是有一定道理的,後者熟悉了比較直接。

在函數是第一類值的前提下,面向對象沒有任何優勢。想要的函數都能隨意賦值了,自然不需要繼承。而設計模式是用來彌補面向對象缺陷的,對lua來說也是沒用的。

又想短小又想簡單又想跨平台又想多線程,這是不可能的,而lua的解決方式也堪稱完美。

標準庫功能少體現在哪呢?我覺得缺什麼自己寫或者去下或者去調C的就行了,弄太多又用不上。說缺庫的可能是站在學院派的角度,而不是站在工程角度,也因此Python對學生更受歡迎吧。

回到正題,我覺得缺點有

局部變數——假設變數默認是局部的,那麼寫global的次數要遠遠小於目前要寫local的次數,並且出錯的次數與嚴重程度遠遠小於默認global。

table中的nil——這種錯誤要麼就按照正確的邏輯執行,要麼就直接爆機,可以運行但是輸出錯誤結果是非常蛋疼的。另外getln和#有功能重複。

不支持整數——沒有整數導致位運算蛋疼,浮點數取有效數字當整數用蛋疼。人們總是希望把相似的東西歸併,然而差異性是無法抹消的。儘管實數和整數有包含關係,但是在一些情況下他們的含義並不相同,比如序數。雖然5.3已經解決了,但是我還是要把這單獨列出來說用下,不要以為自己很聰明就亂合併,要具體問題具體分析(這是給我自己說的請不要亂噴)。

Lua這個語言用簡單的規則實現了幾乎所有現代語言特性,而且幾乎沒有硬性缺陷,除了不夠普及之外可以與C媲美。


建議使用下松鼠腳本,語法類c,介面,大小類似lua,而且改進的全是lua的缺點,目前是我最喜歡的腳本。


就我個人來說,標準庫神馬的,都不是太大問題,因為這些都可以通過庫來解決,我最不爽的反倒是兩點:

一是沒有真正的整數,在很多情況下需要int64,lua就玩不轉了,需要用ludata或者string去模擬,好在5.3打算要解決了。

二是古怪的table,包括 nil 不能作鍵、作為值會丟失,這裡尤其不爽的就是 1-based,而不是 0-based,這明明是讓程序員思考更費勁嘛!我曾經非常希望 5.3 能支持一個新的0-based array類型,看來要失望了。

另外 luajit 和 lua 的分裂,讓人很擔憂。希望 5.3 出來後,能夠重新走回一條路上來。


水一下..

看到的一個項目 dabroz/mruby-simple-game-engine

裡面的slide https://raw.githubusercontent.com/dabroz/mruby-simple-game-engine/master/slides/GIC2016.pdf

開始是從大小,腳本,流行度選擇, 最後剩下lua, mruby.


動態語言的通病,重構起來沒有靜態語言爽、沒有足夠的靜態語言檢查工具。


我的理解是,Lua把自己做的很精簡優雅,然後把複雜留給使用者。

這麼做好還是不好,得看應用場合。

不過這樣就使得Lua不太通用,始終只是一門寄生語言。


C介面太亂了


垃圾回收 不支持分代回收 雖然延時低 但數據一多就完蛋了 lua里分配個1g 慢成狗


lua的文件庫對中文支持不好

。。。。。。。。。。。。。。。

其實這都不是事,本來也就是用lua寫寫邏輯代碼,最頭疼的代碼寫多了,工程複雜了比較難以維護,通過閉包解決的也不是很爽,總之各有利弊吧


缺點嘛,樓上朋友們都說的挺全了,標準庫功能太少、沒有多核支持、沒有unicode支持、c api太難用。還有朋友試圖辯解,其實沒必要,因為這基本上是客觀事實。


昨天剛開始看,很不習慣一點:return語句後面要是end/else/until!

For syntactic reasons, a return can appear only as the last statement of a
block: in other words, as the last statement in your chunk or just before an
end, an else, or an until.

這不是坑爹么!我一開始還以為我理解錯了。


1.全局變數和局部變數

2.沒有continue


推薦閱讀:

為什麼Lua不支持大多數編程語言都有的continue,卻非得支持一般情況下用得很少的 repeat until ?
為什麼很多編程語言用 end 作為區塊結束符,而放棄花括弧?
unity中lua的開發工具?
學習哪些 Functional programming language 能夠拓寬眼界,學到和其他編程範式明顯不一樣的東西?
Lua 為什麼在遊戲編程領域被廣泛運用?

TAG:編程語言 | Lua |