理解 Lua 的那些坑爹特性


Lua 那些坑爹的特性

來源 https://blog.lilydjwg.me/2012/12/29/lua-caveats.36879.html
 

協程只能在 Lua 代碼中使用

協程(coroutine)應該是 Lua 最大的賣點之一了。可是,它有一個在文檔中根本沒有提到過的弱點:只能在 Lua 代碼中使用,不能跨越 C 函數調用界限。也就是說,從 C 代碼中無法直接或者間接地掛起一個在進入這個 C 函數之前已經創建的協程。而 Lua 本身作為一種易於嵌入的語言,必然不時與 C 打交道。

比如以下程序:

 
co.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
c = require ( 'c' )
 
co = coroutine.create ( function ()
   print ( 'coroutine yielding' )
   c.callback( function ()
     coroutine.yield ()
   end )
   print ( 'coroutine resumed' )
end )
 
coroutine.resume (co)
coroutine.resume (co)
 
print ( 'the end' )

C 模塊代碼:

 
c.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include<stdio.h>
#include<stdlib.h>
#include<lua.h>
#include<lualib.h>
#include<lauxlib.h>
 
static int c_callback(lua_State *L){
   int ret = lua_pcall(L, 0, 0, 0);
   if (ret){
     fprintf (stderr, "Error: %s\n" , lua_tostring(L, -1));
     lua_pop(L, 1);
     exit (1);
   }
   return 0;
}
 
static const luaL_Reg c[] = {
   { "callback" , c_callback},
   {NULL, NULL}
};
 
LUALIB_API int luaopen_c (lua_State *L) {
   luaL_register(L, "c" , c);
   return 1;
}

在官方版 Lua 以及 LuaJIT 中會出現「attempt to yield across metamethod/C-call boundary」錯誤。只有打過 Coco 補丁的版本才能正常執行。

 
1
2
3
4
5
6
7
8
9
10
>>> lua5.1 co.lua
coroutine yielding
Error: attempt to yield across metamethod/C-call boundary
>>> luacoco co.lua
coroutine yielding
coroutine resumed
the end
>>> luajit co.lua
coroutine yielding
Error: co.lua:6: attempt to yield across C-call boundary

據說 LuaJIT 已經解決了這個問題,不過我想他們說的是內建函數支持 yield 而已。

在 Lua 5.2 中,提供了新的 API 來支持在 C 中 yield。不過,既然是 C API,當然得改代碼,而且看上去比異步回調更復雜。

幽靈一般的 nil

nil 相當於 Python 中的 None 或者 C 中的 NULL,表示「沒有這個值」的意思。但是,一個神奇的地方在於,所有未定義的變量的值均為 nil。所以,在 Lua 中有空值 nil,但是有時它又不存在:當你嘗試把 nil 值存到表里時,它會消失掉。

另外,當 nil 被傳入接受可變參數的函數時,官方版 Lua 只能通過select('#', ...)獲取參數個數。至於 LuaJIT,很遺憾,沒有辦法。

LuaJIT 中還有這樣一個值,它等於 nil。但是根據 Lua 語言標准,只有 false 和 nil 的值為假。於是,在 LuaJIT 中,兩個相等的量,卻有着不同的真值。它就是 ffi 中的 NULL 指針。

在另外一些地方,也會有其它各種庫定義的 null 值,比如ngx.nullcjson.null。這些空值之間哪些相等哪些不等就難說了。

沒有 continue

Lua 一直不肯添加 continue 關鍵字。作者聲稱不添加不必要的特性。請問有誰認為「repeat ... until」結構比「continue」關鍵字更有必要?於是,凡是本來應當使用 continue 的地方,都不得不弄一個大大的 if 語句:

 
1
2
3
4
5
for line in configfile:
   if line.startswith( '#' ):
     contine
 
   parse_config(line)

在 Lua 中只能這么寫:

 
1
2
3
4
5
6
for line in configfile do
   if string.sub (line, 1 , 1 ) == '#' then
   else
     parse_config(line)
   end
end

所以,Lua 代碼的左邊空白的形狀都是些 45° 或者 135° 的斜線。

錯誤信息的表達

Lua 中,習慣的錯誤表達為,返回兩個值,第一個為 nil 表示發生了錯誤,第二個為字符串,是錯誤信息。字符串形式的錯誤信息顯示給用戶挺不錯的(想想微軟喜歡的長長的錯誤號)。可是,程序里只好用模式匹配去判斷是否發生了指定類型的錯誤。這多么像 VimScript 中的錯誤處理啊。journald 取代 syslog 的重要原因之一就是它存儲的是結構化文本。Lua 錯誤處理最偉大的一點則是我們又回到了字符串匹配。別以為你可以返回一個 table 或者 userdata 來表達錯誤。很多庫可不這么認為。當你的結構化錯誤被..連接時你就會發現這廝沒救了。

下標

別的編程語言下標都從 0 開始。Lua 為了更「人性化」,其下標從 1 開始。其實寫多了也能習慣,除了當通過 ffi 獲得一個 C 數組的時候……

提前返回

return 語句之后必須跟着一個end。於是,很多提前返回的時候只能寫do return end。有意義么?

方法調用

訪問表或者 userdata 的域使用一個點.,連接字符串使用兩個點..。而方法定義和調用時,你需要垂直放置的兩個點——冒號:。它與域訪問的一個點相比,也就多了四個像素,顯示器不干凈或者精神不佳的時候就得小心了!

面向對象

Lua 是不支持面向對象的。很多人用盡各種招術利用元表來模擬。可是,Lua 的發明者似乎不想看到這樣的情形,因為他們把取長度的__len方法以及析構函數__gc留給了 C API。純 Lua 只能望洋興嘆。

結論

Lua 只適合寫寫配置。做純計算用用 LuaJIT 也不錯。復雜的邏輯還是交給專業點的語言吧。

 
 
 

理解 Lua 的那些坑爹特性

 
來源  http://sw.is-programmer.com/2013/1/3/understand-lua-caveats.36905.html

 

按:最近看到了依雲的 文章,一方面,為Lua被人誤解而感到十分難過,另一方面,也為我的好友,依雲沒有能夠體會到Lua的絕妙和優雅之處而感到很遺憾,因此我寫了這篇文章,逐條款地說明了依雲理解中出現的一些問題。希望能夠幫助到大家!
 

1. 協程只能在Lua代碼中使用

 
    是的,協程在當你需要掛起一個C函數的時候無法使用。但是,在提出這個缺陷的時
候,是不是應該想一想: 為什么Lua會有這個缺陷
 
    原因很簡單:這一點完全避不開,這是C的問題,C無法保存函數執行的某個現場用於
返回這個現場繼續執行,因此完全沒有辦法在Lua的協程被喚醒的時候,回到這個現場。
 
    那么怎么辦呢?Lua5.2做出了很優秀的設計。既然無法回到C的現場,那么我們不回
去了,而是采取“事件通知”的方式告訴你,“hey哥們,你前面的邏輯被切了,想辦法
補救吧”,這就是所謂的 CPS——繼續風格的編程。繼續在這里是一個Scheme/Lisp術
語,意思是“當前的操作執行完了以后,下面該做什么?”這種風格是Lua能支持任意
Yield 的必要條件。在C的限制下, 只有這一種方法能突破這個限制。
 
    至於你說的“比異步回調更復雜”,我想你弄混了兩點:1.這只是C API層面的修改
完全不影響到Lua代碼層面,你的Lua代碼完全不必做出任何修改,而且,你對
coroutine的用法 完全錯了!等會兒我會教你coroutine到底怎么用。2.上面提到了,
這是 唯一一種能支持coroutine的方式,既然是唯一一種,就無所謂復雜與否了。3.
我下面會演示給你,為什么說coroutine完全解放了程序員,使用coroutine的代碼會帶來
革命性的簡化。
 
    我們分兩步來說明這個問題:第一步,我們先來看你的例子:你想做的事情是,在執
行 c.callback的時候,能夠yield出來,繼續其他的流程。這里必須要說明,你的API設
計本身就是callback式的,因此這種API本身就犯不着coroutine,Lua本身能完全地處理
。這里我會給出一個支持coroutine的C模塊設計,讓這個模塊能支持coroutine,第二步
,我會告訴你coroutine實際上是用在什么方面的,是如何取代事件回調機制的。在完成
這個說明后,我們來說明coroutine到底有什么好處,為什么說coroutine比事件回調機制
有着  革命性的優秀之處。
 
    你的例子是這樣的:
 
?
co.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
c = require ( 'c' )                
                                  
co = coroutine.create ( function ()
   print ( 'coroutine yielding' )   
   c.callback( function ()         
     coroutine.yield ()           
   end )                          
   print ( 'coroutine resumed' )    
end )                            
                                  
coroutine.resume (co)            
coroutine.resume (co)            
                                  
print ( 'the end' )                
 
    先說一下,將模塊放到全局變量里通常不是一個好主意。所以第一行如果寫成
?
1
local c = require 'c'
    就更好了。
 
    其他的地方倒是沒什么需要修改的了。
 
    再看看你的C模塊代碼:
 
?
c.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include<stdio.h>                                       
#include<stdlib.h>                                      
#include<lua.h>                                         
#include<lualib.h>                                      
#include<lauxlib.h>                                     
                                                          
static int c_callback(lua_State *L){                    
   int ret = lua_pcall(L, 0, 0, 0);                      
   if (ret){                                              
     fprintf (stderr, "Error: %s\n" , lua_tostring(L, -1));
     lua_pop(L, 1);                                      
     exit (1);                                            
   }                                                     
   return 0;                                             
}                                                       
                                                          
static const luaL_Reg c[] = {                           
   { "callback" , c_callback},                             
   {NULL, NULL}                                          
};                                                      
                                                          
LUALIB_API int luaopen_c (lua_State *L) {               
   luaL_register(L, "c" , c);                             
   return 1;                                             
}                                                       
    
    首先,因為這是Lua的C模塊,所以你得聲明這的確是一個C模塊,應該在
        #include <lua.h>
    之前加入這一行:
        #define LUA_LIB
 
    編譯的時候就可以用下面的命令行了:
        gcc -mdll -DLUA_BUILD_AS_DLL c.c -oc.dll
 
    然后,Lua5.2已經沒有luaL_register函數了,因為Lua不鼓勵將模塊設置到全局域,
而luaL_register會做這件事。所以將這行改為:
        luaL_newlib(L, c);
    最后一點不是問題,只是一個小建議:Lua只是會用luaL_Reg里的內容,但是卻不會
保留里面的任何內容,所以你可以直接將其放在luaopen_c里面,並去掉static,這樣可
以節省一點內存。
 
    我們來看看一個支持coroutine的C模塊應該怎么寫:
 
?
c.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include<stdio.h>                                                    
#include<stdlib.h>                                                   
                                                                       
#define LUA_LIB /* 告訴Lua,這是一個LIB文件 */                       
#include<lua.h>                                                      
#include<lualib.h>                                                   
#include<lauxlib.h>                                                  
                                                                       
static int c_cont(lua_State *L) {                                    
   /* 這里什么都不用做:因為你的原函數里面就沒做什么 */               
   return 0;                                                          
}                                                                    
                                                                       
static int c_callback(lua_State *L){                                 
   /* 使用 lua_pcallk,而不是lua_pcall */                             
   int ret = lua_pcallk(L, 0, 0, 0, 0, c_cont);                       
   if (ret) {                                                          
     fprintf (stderr, "Error: %s\n" , lua_tostring(L, -1));             
     lua_pop(L, 1);                                                   
     exit (1);                                                         
   }                                                                  
   /* 因為你這里什么都沒做,所以c_cont里面才什么都沒有。如果這里需要做
    * 什么東西,將所有內容挪到c_cont里面去,然后在這里簡單地調用      
    * return c_cont(L);                                               
    * 即可。                                                          
    */                                                                
   return 0;                                                          
}                                                                    
                                                                       
static const luaL_Reg c[] = {                                        
   { "callback" , c_callback},                                          
   {NULL, NULL}                                                       
};                                                                   
                                                                       
LUALIB_API int luaopen_c (lua_State *L) {                            
   /* 使用新的 luaL_newlib 函數 */                                    
   luaL_newlib(L, c);                                                 
   return 1;                                                          
}
    
    現在,你的例子可以完美運行了:
 
?
1
2
3
4
lua  -- co.lua    
coroutine yielding
coroutine resumed 
the end           
    
    我們看到,讓C模塊支持yield是非常簡單的:首先,你需要將lua_call/lua_pcall改
成對應的k版本,將函數其后的所有內容剪切到對應的cont函數里去,然后將原先的內容
改為return func_cont(L);即可。
 
    為什么要這么設計API?上面說了,這是為了解決C 自身的問題,如是而已。
 
    現在我們來討論第二個問題:Lua的coroutine用在什么地方呢?
 
    假設我們要書寫游戲的登陸邏輯,我們需要干這樣的事情:
        1. 登陸游戲
        2. 獲取玩家角色數據
        3. 讓玩家移動到上次退出前的坐標
 
    如果是事件回調引擎,你會怎么設計API呢?可能是這樣的:
 
?
1
2
3
4
5
6
7
8
9
function do_login(server)                                                
     server:login( function (data)                                          
         -- 錯誤處理先不管,假設有一個全局處理錯誤的機制(后面會提到,實際
         -- 上就是newtry/protect機制)                                    
         server:get_player_info( function (data)                            
             player:move_to(data.x, data.y)                               
         end )                                                             
     end , "username" , "password" )                                         
end
 
    看到了么?因為登陸需要等待網絡請求,而等待的時候你不能把事件循環給阻塞了,
所以你不得不用回調機制,但是,一旦你一次要做幾件事情,回調立即就會讓你的代碼狼
狽不堪。這還只是簡單的順序代碼。如果是判斷或者是循環呢?我告訴你,上面的代碼是
一個真實的例子,是我以前設計的手機網游里面關於登陸部分的實際例子,而另一個例子
是在客戶端批量購買N個道具!可以想象這會是一個很復雜的遞歸代碼了,而實際上你僅
僅是想做for在做的事情而已!
 
    那么怎么辦呢?coroutine提供了解決這個問題的一個極端優雅的辦法。我們想想最
優雅的設計會是什么樣子的:
 
?
1
2
3
4
5
function d_login(server)                 
     server:login( "username" , "password"
     local data = server:get_player_info()
     player:move_to(data.x, data.y)       
end                                      
 
    是不是簡單多了?慢着!看起來login等函數是阻塞的,這樣的阻塞難道不會阻塞事
件循環,導致界面僵死么?好!現在coroutine上場了!看看我們是如何實現login的!
 
?
1
2
3
4
5
6
7
8
9
10
11
local current                                         
function server:login(name, password)                 
     assert ( not current, "already send login message!" )
     server:callback_login( function (data)              
         local cur = current                           
         current = nil                                 
         coroutine.resume (cur, data)                   
     end , name, password)                              
     current = coroutine.running ()                     
     coroutine.yield ()                                 
end                                                   
 
    看到了嗎?login先用正常的方式調用了基於回調的callback_login,然后設置當前
在等待的coroutine為自身,最后yield掉自己。在回調執行的時候,回調會resume那個上
次被yield掉的coroutine,這樣就完美的支持了阻塞的語法並且還能夠滿足事件循環的約
束! 能夠重新整理程序的執行流程,這就是coroutine的強大之處。最奇妙的是,在
這個設計之中,回調中唯一會做的事情只有resume,而不是yield,這意味着**即使不修
改一行代碼,現有的模型也可以完美支持這個模式**!
 
    可以看出將回調模式的函數改造成協程模式的函數是很簡單的,我們甚至可以寫一個
高階函數來做這件事:
    
?
1
2
3
4
5
6
7
8
9
10
11
12
13
function coroutinize(f, reenter_errmsg)    
     local current                          
     return function (...)                   
         assert ( not current, reenter_errmsg)
         f( function (...)                    
             local cur = current            
             current = nil                  
             coroutine.resume (cur, ...)     
         end , ...)                          
         current = coroutine.running ()      
         coroutine.yield ()                  
     end                                    
end                                        
 
    這樣,上面的login函數就很簡單了:
 
        server.login = coroutinize(server.login)
 
    看到Lua在表達復雜邏輯時的巨大優勢了嗎?coroutine機制同樣也是可以支持函數重
入的:如果一個函數被調用多次,那么對應被調用的回調調用時,對應的那個coroutine
會被resume。至於如何實現,就交給讀者作為練習了。提示:Programming in Lua這本書
已經說明了該如何去做。
 
    我們總結一下:
        1. coroutine無法穿越C邊界是C語言的固有缺陷,Lua無法在保持其代碼是Clean
           C的前提下完成這個impossible的任務。
        2. 那么,要支持這個特性,就只有要求C模塊的編寫者能采用CPS的方式編程了
           。當然Lua的代碼可以完全不做任何修改。
        3. 而,coroutine很少需要在C函數內部yield(可能有實際場景會需要,但事實
           是在我所書寫的上萬行的Lua富coroutine的代碼中,完全沒有用到過這種策
           略)。
        4. 如果你能深入了解coroutine,你會發現即使coroutine無法在C內部yield,
           coroutine依然可以展現其絕大多數的威力。
        5. Lua本身的設計可以讓Lua在表現極端復雜的邏輯關系時游刃有余。
 

2. 幽靈一般的 nil

 
    我不否認,在我剛剛學習Lua的時候,我的確被nil坑過很多遍。我們先拋棄掉luaJIT
關於NULL設計的問題(這個設計本身也是一種無奈,而且LuaJIT畢竟並不能完全繼承Lua
作者對Lua的理念),先來看看nil究竟是什么——從nil中,我學習到了,在遇到坑爹特
性之前,先不要急着抱怨,想想為什么作者會設計這么坑爹的特性。要么作者是比你低能
的傻逼,要么這么設計就的確是有充分的考慮和不得已的苦衷的。這點你想到過嗎?
 
    nil是一個表示“沒有”的值。是的,就是真的“沒有”,因此nil本身就是一個幽靈
——它除了表示“這里沒有東西”以外,沒有其他的任何含義!它不是None(None是一個
表示“空”的對象),它也不是NULL(NULL表示沒指向任何地方的指針——總所周知指針
本身必定是有值的,哪怕那個值是NULL)。Lua的作者十分聰明的將“沒有”這個概念也
引入了語言,並且還保持了語言的一致性:請問,將“沒有”存入一個表里面,它如果不
消失,還能發生什么事呢?
 
    那么如何表示“空”或者“沒有指向任何地方的引用”呢?兩個辦法,你可以存入
false,或者可以用下面這個巧妙的方法:
 
?
1
2
3
4
5
6
7
undefined = {}                             
-- 指定一個全局變量存在,但不指向任何地方:
a = undefined                              
-- 判斷這個全局變量是否不指向任何地方:    
if a == undefine then ... end              
-- 徹底刪除這個變量:                      
a = nil                                    
 
    看!Lua可以靈活的控制一個變量的實際作用域!讓一個變量真正的憑空消失掉!這
一點即使是C或者C++都是做不到的(變量只有在作用域外才會消失),這也是Lua強大的
靈活性的一個佐證。
 
    千萬不要弄錯了,nil代表“沒有”,它不代表“空”,也不代表“沒有被初始化的
值”,它只是沒有而已。因此它就應該是幽靈般的。這是Lua的語言設計一致性帶來的優
勢,而不是坑爹的特性。如果說學不會的特性就是坑爹的特性,那么是不是C語言的指針
也是坑爹的特性呢?
 
    其次,各種庫定義的null值,本質上是代表微妙但不同的東西。仔細地體會其中的不
同,能讓你更得心應手的使用那些庫。如果你在這些庫的互操作上感到困擾,請給庫的作
者寫郵件抱怨:Lua有一個很熱情友好的社區!
 

3. 沒有continue

 
    是的,Lua一直不肯加入continue。為什么呢?因為repeat until。而為什么強調“
不添加不必要的特性”的Lua作者會舍棄掉“那么常見”的continue,卻保留不那么常見
的repeat呢?是Lua的作者傻么?
 
    不是。這是經過仔細設計的。我先告訴你答案,再仔細地分析:事實上,在加入
repeat以后,continue是邏輯上不可能實現的,而repeat語句實現了一個用其他的特性完
全無法取代的特性。
 
    注意看repeat的結構:
 
        repeat <block> until <exp>
 
    問題就在<exp>上了。Lua規定,<exp>是在<block>的作用域下計算的!這也就意味着
?
1
2
local a = true          
repeat a = false until a
    會是一個死循環!看到了么?這種“表達式在一個block內的上下文計算”的循環特
性,是無法被其他任何特性模擬的!這種特性導致Lua作者為了語言的完整性,不得不將
repeat語句添加入Lua語言。
 
    那么continue呢?花開兩朵,各表一枝,我們先介紹一下Lua5.2的新特性:goto語句
。lua5.2支持跳轉語句了!看看你之前的那個例子吧,在沒有continue的情況下,我改如
何寫那個循環呢?答案是這樣:
 
?
1
2
3
4
5
6
7
for line in configfile do                
     if string.sub (line, 1 , 1 ) == '#' then
         goto next                        
     end                                  
     parse_config(line)                   
     :: next ::                             
end                                      
 
    看上去有點小題大做,連goto都用上了!呵呵,其實還有一個細節你不知道哦,在
Lua5.2里,甚至連break都沒有了!break語句只是goto break的一個語法糖而已。而
break標簽會被自動插入到你的源代碼中。
 
    那么,你可能會問了,事已至此,為什么不也加個continue的語法糖呢?畢竟break
都是語法糖了!好,我們試着在repeat里面用一下我們手寫的“continue”:
 
?
1
2
3
4
5
6
7
8
9
10
local i                          
repeat                           
     if i == 5 then               
         goto next                
     end                          
     local j = i * i              
     print ( "i = " ..i.. ", j = " ..j)
     i = i + 1                    
     :: next ::                     
until i == 10                    
 
    這個例子造的有點刻意了,但是至少也有continue的意思了吧!好,現在執行一下—
—出錯了……
 
?
1
2
3
4
lua  -- "noname\2013-01-03-1.lua"                                                       
lua: noname\2013-01-03-1.lua:10: <goto next> at line 4 jumps into the scope of local 'j'
shell returned 1                                                                        
Hit any key to close this window...
 
    這是怎么回事??
 
    我們知道,在C里面,goto可以跳入任何地方,包括跳過變量的初始化,這會導致一
個變量可能在用的時候,是未初始化的。Lua為了避免這樣的錯誤,要求goto不允許跳入
一個變量的作用域內部!而看看我們的代碼,goto跳入了變量j的內部!
 
    在這種情況下,是根本沒有辦法continue的!
 
    這就是Lua作者不提供continue的真實意思。continue是根本不可能實現的。因為從
完整性考慮,必須提供支持作用域擴展的repeat語句(記得C/C++對for的三個子語句中的
作用域規定么?),而continue可能會在repeat的條件表達式中用到的變量還沒有初始化
的時候,就開始條件表達式的計算!這是不允許的!
 
    現在我們知道了:
        1. 不是Lua作者故意不提供continue,而是continue和當前的一個關鍵特性沖突
           了,導致continue語句完全無法實現。
        2. 為了彌補這個問題,Lua作者為Lua加入了goto語句。然而該有的限制仍然存
           在,但是編譯器會為你檢查這個限制!
        3. 所以,現在在Lua里的大多數情況下,你仍然能使用你自己手工打造的
           continue,而且功能更為強大(labeled break,labeled continue都是可以
           模擬出來的)。
 
    關於goto語言可能的壞處以及作者的考慮,請參考Lua作者的novelties-5.2.pdf文件。
 
    對了,還要說一句:有讀者可能為問:既然Lua已經把break做成語法糖了,為什么不
把continue也做成語法糖呢?如果遇到不合法的情況,直接出錯不行么?
 
    這個問題我也沒想明白。也許會有自己的原因吧,不過如果把這個想法當作Lua的坑
爹設計也未嘗不可以,不過其“坑爹指數”已經大為降低了。
 
 

4. 錯誤信息的表達

 
    我只想說一句話:其實大多數在預見到會對錯誤進行處理的場合里面,錯誤的返回方
式其實並不是nil, errmsg,而是nil, errmsg, errno。別的你懂了。
 

5. 下標

 
    參看 novelties-5.2.pdf,說的非常明白了。
 

6. 提前返回

 
    這是一個語法問題,事實上return語句不跟着end的話,那么編譯器就根本無法編譯
return語句了。這是Lua“行無關語法”的一個必然折衷,我開始也不爽,但事實是,在
我數萬行的Lua開發中,除了測試必要要注釋一部分代碼以外,我根本沒用過do return
end這種表達——至於為什么,你實際開發一下就知道了:因為這種代碼一定會導致完全
無法被執行到的死代碼。
 

7. 方法調用

8. 面向對象

 
    這兩點恰好就是Lua的優勢啊!!有時間我會寫一篇文章來討論。這實際上是Lua能以
比其他語言小巧靈活得多地去處理復雜邏輯的一個必然原因了。這里只說一點:Lua5.2中
,表所具有的元方法已經和C API能處理的完全一樣多了。純Lua已經不必對着__len和
__gc而望洋興嘆了。
 
    關於這一點,MikePall(LuaJIT實現者)還專門和Lua作者吵了一架,因為讓表支持
__gc會導致luaJIT的jit編譯非常難寫= =||||
 

9. 結論

 
    我開始學習Lua的時候,也幾乎得到了跟你一樣的結論。然而,在長達兩年的Lua開發
中,我逐漸認識到了Lua的美,認識到了Lua實現的優雅和嚴謹。現在如果有新手想學習C
語言開發的訣竅和技巧,我通常會建議他去拜讀Lua的C實現源代碼。Lua的實現太優雅了
。而Lua的設計也凝聚着作者的一點一滴的心血。Lua精准絕妙的設計是Lua強大的表達能
力的表現。繼續學習下去吧,我向你保證,你一定會發現,Lua實際上腳本語言里面表達
能力最強,概念最統一,設計最優雅的語言了。Lua無愧腳本語言之王!
 
    
 
 
    
 
Category:  未分類 | Tags:  Lua | Read Count: 36861
Avatar_small
依雲 說: 
5 年前

關於協程,我當然知道協程該怎么用。Lua C API 確實有些細節上不太清楚,文檔太簡略了。pcallk 只能解決一次 yield 吧?如果 yield 的次數不定該怎么辦?我有個庫的函數,在運行過程中可能需要調用一個回調函數來取某些數據。在 Lua 綁定中,這個回調函數就是調用一個 Lua 函數,然后由於涉及網絡操作,它是會 yield 不定次數的。

你說的所有這些,要么是 LuaJIT 2.0.0 還沒實現的特性(LuaJIT 比 Lua 快太多了),要么是要求作者對 Lua 該怎么編程很熟悉(如果我接手的那些代碼是你寫的就好了)。至於表達能力,還是不要太強的好,不然每個人的錯誤返回方式和面向對象的實現都不一樣,概念是統一了,實現千差萬別、各不相容。

沒錯,「do return end」就是調試時用的。

Avatar_small
亞彌 說: 
5 年前

@依雲: 恩,說句實話,只看reference的確很難搞明白k系列函數內部的核心思想。我是一開始就跟着郵件列表的討論才比較清楚的。不過你真的可以看看novelties-5.2.pdf,這里面有很詳細的說明。 
另外不明白“一次yield”和“多次yield”有什么區別。只要用了k系列函數,你多少yield都沒問題的,因為Lua自己會幫你維護Lua內部yield時候的狀態。無論你如何yield,回到C層面(即從內部的coroutine返回)只會有一次,因此k系列函數一定能做到你想要的,而且並不需要特別的設計。 
你仔細看看LuaJIT,很多特性已經實現了,包括goto。k系列函數沒實現是基於兩個原因:1.LuaJIT關注純Lua應用,甚至用ffi庫取代了C API的必要性;2.LuaJIT因為與Lua作者的巨大分歧(郵件里面吵了好幾架),所以不打算實現5.2兼容了。至少短期內是不想的。sigh……快的話,其實快不了多少,只是科學計算方面的確快了很多,如果你的代碼是C模塊密集的,那么LuaJIT很難提高效率,其次是如果你用了NYI的特性,那么也是不會快的(比如字符串模式匹配和coroutine),從我的經驗看,網游邏輯書寫用luaJIT對效率的提升不大,甚至可能比原Lua更慢。 
表達能力問題的確是個雙刃劍,但有個朋友說得好“做得到總比做不到好”,這個就看怎么解讀了。 
錯誤返回是有標准模式的,文章里面提到了newtry/protect模式,不過寫到后來寫忘了= =有時間補上吧,OO的話也是有標准模式的,而且是兩套。關鍵是,因為底層概念統一,所以即使是千差萬別的實現,最終也一定是兼容的。你如果處理過實現之間的糾葛就會體會到底層概念統一帶來的巨大好處。 
do return end的話也就是一個詞和三個詞的區別吧……sigh……就當多打字了,實在不行做個imap或者iab唄……

Avatar_small
Wayne 說: 
5 年前

哇,偶像你也在這里!!

Avatar_small
Larry Xu 說: 
5 年前

newtry/protect?? 
luasocket中的那套,我當時看了也覺得蠻有意思的 
可否補充介紹下實際過程的使用方式

Avatar_small
4T-Shirt 說: 
5 年前

你好,能給我lua郵件列表的郵箱嗎?謝謝~~

Avatar_small
wtyqm 說: 
4 年前

您好,想問下,如果想在pcall里使用coroutine,有什么辦法嘛? 文中的protect,指的就是luaSocket里那種封裝pcall的方式吧

Avatar_small
太陽神上 說: 
4 年前

我覺得 lua 還有一個坑爹特性,table 當哈希表時,無法以 O(1) 的時間復雜度取得其元素個數。

Avatar_small
荒野無燈 說: 
4 年前

從lily那里過來的。看了你這文章,受益頗多。

Avatar_small
skyblue 說: 
4 年前

求解釋coroutinize中這部分的必要性

local cur = current 
current = nil

Avatar_small
MlXiangry 說: 
3 年前

local a = true 
repeat a = false until a == false 
這樣吧

Avatar_small
davidfeng 說: 
3 年前

關於continue的例子,我猜樓主的意思是這樣吧?

少寫了一個local?

local a = true 
repeat 
local a = false 
until a

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM