Lua 沒有提供任何明確的套件機制。但是,我們可以使用該語言提供的基本機制輕鬆實作它們。實際上,有幾種方法可以做到這一點,這會造成一個問題:沒有標準方法可以在 Lua 中撰寫套件。此外,由您來遵循規則;既沒有固定的方式來實作套件,也沒有固定的操作來操作它們。
lua
前綴開頭。)儘管很幼稚,但這是一個相當令人滿意的解決方案(至少它沒有阻止人們在大型專案中使用 C)。
在 Lua 中,一個更好的解決方案是使用表格實作套件:我們只需要將我們的識別碼作為鍵值放在表格中,而不是作為全域變數。此處的主要重點是,我們可以將函式儲存在表格中,就像任何其他值一樣。例如,假設我們正在撰寫一個函式庫來處理複數。我們將每個數字表示為一個表格,其中包含欄位 r
(實部)和 i
(虛部)。為了避免污染全域命名空間,我們將在充當新套件的表格中宣告所有新的運算
Complex = {} Complex.i = {r=0, i=1} function Complex.new (r, i) return {r=r, i=i} end function Complex.add (c1, c2) return {r=c1.r+c2.r, i=c1.i+c2.i} end function Complex.sub (c1, c2) return {r=c1.r-c2.r, i=c1.i-c2.i} end function Complex.mul (c1, c2) return {r = c1.r*c2.r - c1.i*c2.i, i = c1.r*c2.i + c1.i*c2.r} end function Complex.inv (c) local n = c.r^2 + c.i^2 return {r=c.r/n, i=c.i/n} end
有了這個定義,我們可以使用任何複雜的運算來限定運算名稱,如下所示
c = Complex.add(Complex.i, Complex.new(10, 20))
使用表格來表示套件並無法提供與實際套件完全相同的功能。在 Lua 中,我們必須在每個函式定義中明確放入套件名稱。此外,呼叫同一個套件內部其他函式的函式必須限定被呼叫函式的名稱。我們可以使用套件的固定區域名稱(例如,Public
)來改善這些問題,然後將此區域變數指定給套件的最終名稱。遵循此準則,我們可以像這樣撰寫之前的定義
local Public = {} Complex = Public -- package name Public.i = {r=0, i=1} function Public.new (r, i) return {r=r, i=i} end ...每當函式呼叫同一個套件內部的其他函式(或每當它遞迴呼叫自身)時,它都應該透過套件區域名稱的向上值來存取被呼叫的函式。例如
function Public.div (c1, c2) return %Public.mul(c1, %Public.inv(c2)) end遵循這些準則,兩個函式之間的連線就不會依賴套件名稱。此外,在整個套件中,我們只有一個地方會撰寫套件名稱。
通常,套件內部所有名稱都是外傳的;也就是說,它們可以被套件的任何用戶端使用。然而,有時在套件中使用私人名稱會很有用,也就是說,只有套件本身才能使用的名稱。一個方便的方法是為套件中的私人名稱定義另一個區域表格。這樣,我們就可以在兩個表格中發布套件,一個用於公開名稱,另一個用於私人名稱。由於我們將公開表格指定給全域變數(套件名稱),因此其所有組成部分都可以從外部存取。但是由於我們沒有將私人表格指定給任何全域變數,因此它仍鎖定在套件內部。為了說明此技術,讓我們在範例中新增一個私人函式,用來檢查值是否為有效的複數。我們的範例現在看起來像這樣
local Public, Private = {}, {} Complex = Public function Private.checkComplex (c) assert((type(c) == "table") and tonumber(c.r) and tonumber(c.i), "bad complex number") end function Public.add (c1, c2) %Private.checkComplex(c1); %Private.checkComplex(c2); return {r=c1.r+c2.r, i=c1.i+c2.i} end ...
那麼,這種方法的優缺點是什麼?套件中的所有名稱都存在於個別的命名空間中。套件中的每個實體都清楚標示為公開或私人。此外,我們有真正的隱私:私人實體在套件外部無法存取。此方法的主要缺點是在同一個套件內部存取其他實體時其冗長性:每個存取都需要一個前綴(%Public.
或 %Private.
)。儘管冗長,這些存取相當有效率;而且我們可以透過為這兩個變數提供較短的別名(例如 local E, I = Public, Private
)來減輕這種冗長性。還有一個問題是,每當我們在公開和私人之間變更函式的狀態時,我們都必須變更前綴。儘管如此,我整體上喜歡這種方法。對我來說,負面的一面(其冗長性)已經被語言的簡潔性所彌補。畢竟,我們可以在不需要語言的任何額外功能的情況下實作一個相當令人滿意的套件系統。
使用表格來實作套件的一個明顯好處是,我們可以像操作其他表格一樣地操作套件,並使用 Lua 的所有功能來建立額外的設施。可能性是無窮無盡的。在此,我們只會提供一些建議。
我們不需要一次定義套件的所有公開項目。例如,我們可以在一個獨立的區塊中,為我們的 Complex
套件新增一個新項目
function Complex.div (c1, c2) return %Complex.mul(c1, %Complex.inv(c2)) end(但請注意,私有部分限制在一個檔案中,我認為這是一件好事。)相反地,我們可以在同一個檔案中定義多個套件。我們只需要將每個套件包在一個
do ... end
區塊中,這樣它的 Public
和 Private
變數就會限制在那個區塊中。
如果我們要經常使用一些運算,我們可以給它們全域 (或區域) 名稱
add = Complex.add local i = Complex.i c1 = add(Complex.new(10, 20), i)或者,如果我們不想一直寫整個套件名稱,我們可以一次給整個套件一個較短的區域名稱
local C = Complex c1 = C.add(C.new(10, 20), C.i)
很容易寫一個函式來開啟整個套件,將其所有名稱放入全域命名空間中
function openpackage (ns) for n,v in ns do setglobal(n,v) end end
openpackage(Complex) c1 = mul(new(10, 20), i)如果你在開啟套件時害怕名稱衝突,你可以在指定之前檢查名稱
function openpackage (ns) for n,v in ns do if getglobal(n) ~= nil then error(format("name clash: `%s' is already defined", n)) end setglobal(n,v) end end
因為套件本身就是表格,我們甚至可以巢狀套件;也就是說,我們可以在另一個套件中建立一個完整的套件。但是,這種設施很少需要。
通常,當我們寫一個套件時,我們會將其所有程式碼放在一個單一檔案中。然後,要開啟或匯入一個套件 (也就是,讓它可用),我們只要執行那個檔案即可。例如,如果我們有一個檔案 complex.lua
,其中定義了我們的複數套件,則指令 dofile("complex.lua")
將會開啟套件。為了避免在多次載入套件時浪費,我們可以在套件開始時檢查它是否已經載入
if Complex then return end local Public, Private = {}, {} Complex = Public ...現在,如果你在
Complex
已經定義好的時候執行 dofile("complex.lua")
,則整個檔案都會被略過。(注意:Lua 4.1 中的新函式 require
將會讓這個檢查過時。)