類別與方法

lua-users home
wiki

[!] 版本公告:本教學使用 Lua 4.0 的標籤與標籤方法概念,它們已在 Lua 5.0 中被元表和元方法所取代。請參閱 LuaClassesWithMetatable

類別表

資料表可用作關聯性陣列。此功能允許我們在類別表中儲存函式,亦即將函式名稱對應至函式。

A = {}

function A:add(x,y)
  return x+y
end

print( A:add(1,2) )  -- prints 3
在這個範例中,我們只有一個 A 類別實例。如果我們想建立另一個實例,我們可以建立一個資料表 be(例如 B={} ),並將 A 中的所有方法複製至 B

標籤方法

可以使用 Lua 的標籤方法建立類別的多個實例。這些標籤方法可用於將來自類別實例的函式要求重新導向至類別函式表。亦即我們不再需要將所有類別函式複製至類別的每個實例。例如:

-- settag() returns a table "{}" which has been tagged with a new tag value
A = settag({},newtag())

-- use the index tag method to redirect a request for a function to the 
-- class function table
settagmethod(tag(A),"index", function(t,f) return %A[f] end)

function A:new(x,y)  -- create an instance of class A
  local t = {x=x,y=y}
  settag(t,tag(A))  -- tag the new table to tell it what its class type is
  return t
end

function A:add()
  print (self.x+self.y)
end

function A:sum(a)
  assert(tag(self)==tag(a)) -- check they are same class
  print (self.x+a.x,self.y+a.y)
end

a = A:new(7,9)  -- new instance of A
a:add()

b = A:new(2,4)  -- new instance of A
b:add()

a:sum(b)  -- "sum" of 2 instances
然而,顯示的範例有些問題。
settagmethod(tag(A),"index", function(t,f) return rawget(%A,f) end)
a.number = 123
a:number()  -- try and call x
這會導致錯誤,因為我們嘗試呼叫一個數字。
a.sum = 7
print( a.sum )  -- should this print <function> or 7 ? (it prints 7)

解決名稱衝突

這個問題無法解決,因為呼叫請求不會告知標籤方法需要哪種類型。我們可以選取一個優先選項,例如優先使用類別實例值,而非函式表值。(這麼做我們可以支援函式重載)。

如果函式表優先,我們可以使用取得資料表標籤方法,在函式表 A 中先檢查一個方法是否存在,例如:

settagmethod(tag(A), "gettable", 
          function(t,k)
            if rawget(%A,k) then
              return rawget(%A,k)
            else
              return rawget(t,k)
            end
          end )

統一方法:Edgar Toernig 在 Sol(Lua 的分支版本)中透過導入統一方法解決了這個問題。在此,函式/方法表與標籤相關聯。發生資料表查詢時會發生什麼事,取決於查詢的呼叫方式。亦即 a.fooa:foo 有不同的含意。a.foo 尋找一個稱為「foo」的成員,而 a:foo 在標籤的函式表中尋找「foo」方法。因此,您可以擁有兩個成員,一個是資料,一個是函式,愉快地並排共處,沒有衝突。

為何需要將方法和「資料」分開在不同的命名空間? Sol 的統一方法很酷,允許將方法和資料分開,還提供了一個比標籤方法更簡單的系統。然而,我認為它的主旨應該是讓類別方法能在沒有每個表格持有自己副本的情況下被實作。如果答案是因為某些類別實體需要允許最終使用者直接新增任意的資料欄位,我會說這可能不是好的設計,而這些操作應該當成獨立函式來實作,而非類別方法,或者欄位存取應該透過受控的介面來進行。(我查看了Python 字典。這個問題難道不是因為嘗試在 Python 中模擬某些介面,而非提供適合 Lua 的介面所導致的嗎?)–JohnBelmonte

我不知道你所指採用受控介面的替代實作是什麼。這將能解決 t.foot:foo 之間的問題(並允許儲存任意資料)嗎?這看起來像是現有的 Lua 標籤方法系統,還是經過修改過的?我不認為這只是嘗試模擬 Python 的結果,這裡存在衝突。也就是說,你的實作受到了限制。我原本以為 meta 機制應該是靈活到足以實作這個功能的。這是實作風格的問題嗎?這個問題可以使用獨立函式來避免,但我不認為那是很簡潔的做法(而且在這個範例中,不像 Python)––NDT

舉一個以 C++ 編寫的容器為例。欄位存取是有受控的––透過 operator[] 或專門的成員函式。欄位存取和非欄位存取之間沒有模糊地帶。Lua 的標籤方法很有限。你無法區分 table[x] 用於存取欄位,以及 table.x / table:x 用於呼叫方法。你Python 字典問題有一個簡單的解決辦法,就是放棄使用原生的表格語法來存取元素,並依賴 getset 等方法。這也會讓實作簡單得多!––JohnBelmonte

但是那不是 Python 字典的樣子啊 :-) 我知道 Lua 不是 Python。Sol 可以讓我做我想做的,但 Lua 限制了我。我可以使用許多不同的方式變通代碼讓它在沒有衝突的情況下運作,但不是我想要的方式 :-((或許應該新增一些變體...)。那是我在上面指出的,而本文的傾向很可能是支持統一方法 :-)。你只能在 Lua 中實作特定物件到某個程度。我認為它和 table.n 問題有點類似––表格是多用途的,但有它的限制?––NDT

「你只能在 Lua 中實作特定物件到某個程度。」我不同意這點。你遇到的問題純粹是語法上的問題。Lua 沒有奇幻的巨集語言。但它是一種強大的函數式語言。堅持使用函數式介面,一切就會很順利。––JohnBelmonte

「Lua 能透過語法來實作物件,但範圍有限」會更精確。 模仿 Python 字典不論是在功能上或語法上,並盡可能地接近,只是一個產生一些有趣結果的練習而已。 Lua 透過不同的語法/介面模擬功能並不會是個問題。 此頁面上的概念只是簡單地列出一些概念和限制。--NDT


另見:SampleCodePythonListsPythonDictionaries 包含類別範例。
RecentChanges · 偏好設定
編輯 · 歷程
最後編輯時間 10 月 23, 2009 下午 8:08 GMT (diff)