第一版是為 Lua 5.0 編寫的。雖然在很大程度上仍然與後續版本相關,但有一些差異。
第四版針對 Lua 5.3,可在 Amazon 和其他書店購買。
購買本書,您還可以 支援 Lua 專案


13.1 – 算術元方法

在本節中,我們將介紹一個簡單的範例來說明如何使用元表。假設我們使用表格來表示集合,並使用函數來計算兩個集合的聯集、交集等。如同我們對清單所做的那樣,我們將這些函數儲存在表格中,並定義一個建構函式來建立新的集合

    Set = {}
    
    function Set.new (t)
      local set = {}
      for _, l in ipairs(t) do set[l] = true end
      return set
    end
    
    function Set.union (a,b)
      local res = Set.new{}
      for k in pairs(a) do res[k] = true end
      for k in pairs(b) do res[k] = true end
      return res
    end
    
    function Set.intersection (a,b)
      local res = Set.new{}
      for k in pairs(a) do
        res[k] = b[k]
      end
      return res
    end
為了協助檢查我們的範例,我們也定義一個函數來列印集合
    function Set.tostring (set)
      local s = "{"
      local sep = ""
      for e in pairs(set) do
        s = s .. sep .. e
        sep = ", "
      end
      return s .. "}"
    end
    
    function Set.print (s)
      print(Set.tostring(s))
    end

現在,我們想要讓加法運算子 (`+´) 計算兩個集合的聯集。為此,我們將安排所有表示集合的表格共用一個元表,而這個元表將定義它們如何對加法運算子做出反應。我們的步驟是建立一個常規表格,我們將其用作集合的元表。為了避免污染我們的命名空間,我們將其儲存在 Set 表格中

    Set.mt = {}    -- metatable for sets
下一步是修改建立集合的 Set.new 函數。新版本只有一個額外的程式行,它將 mt 設定為它所建立的表格的元表
    function Set.new (t)   -- 2nd version
      local set = {}
      setmetatable(set, Set.mt)
      for _, l in ipairs(t) do set[l] = true end
      return set
    end
在那之後,我們使用 Set.new 建立的每個集合都將具有相同的表格作為其元表
    s1 = Set.new{10, 20, 30, 50}
    s2 = Set.new{30, 1}
    print(getmetatable(s1))          --> table: 00672B60
    print(getmetatable(s2))          --> table: 00672B60

最後,我們將所謂的元方法新增到元表,一個描述如何執行聯集的欄位 __add

    Set.mt.__add = Set.union
每當 Lua 嘗試新增兩個集合時,它將呼叫這個函數,並將兩個運算元作為引數。

有了元方法,我們可以使用加法運算子來執行集合聯集

    s3 = s1 + s2
    Set.print(s3)  --> {1, 10, 20, 30, 50}
類似地,我們可以使用乘法運算子來執行集合交集
    Set.mt.__mul = Set.intersection
    
    Set.print((s1 + s2)*s1)     --> {10, 20, 30, 50}

對於每個算術運算子,在元表中都有對應的欄位名稱。除了 __add__mul 之外,還有 __sub(用於減法)、__div(用於除法)、__unm(用於負數)和 __pow(用於指數)。我們也可以定義 __concat 欄位,以定義串接運算子的行為。

當我們新增兩個集合時,無需質疑要使用哪個元表。但是,我們可以撰寫一個表達式,將具有不同元表的兩個值混合在一起,例如這樣

    s = Set.new{1,2,3}
    s = s + 8
要選擇元方法,Lua 會執行下列動作:(1) 如果第一個值具有包含 __add 欄位的元表,Lua 會使用這個值作為元方法,與第二個值無關;(2) 否則,如果第二個值具有包含 __add 欄位的元表,Lua 會使用這個值作為元方法;(3) 否則,Lua 會引發錯誤。因此,最後一個範例會呼叫 Set.union,而表示式 10 + s"hy" + s 也是如此。

Lua 對於這些混合型別不感興趣,但我們的實作會感興趣。如果我們執行 s = s + 8 範例,我們會在 Set.union 內部取得錯誤。

    bad argument #1 to `pairs' (table expected, got number)
如果我們想要更清楚的錯誤訊息,我們必須在嘗試執行操作之前明確檢查運算元的型別。
    function Set.union (a,b)
      if getmetatable(a) ~= Set.mt or
         getmetatable(b) ~= Set.mt then
        error("attempt to `add' a set with a non-set value", 2)
      end
      ...  -- same as before