可變函式

lua-users home
wiki

我們從函式程式設計知道,實作為一類物件的函式和封包能達成各種語意。以下是關於如何使用封包實作 Lua 表格和物件的語意,且不使用任何 Lua 表格。

我們首先建立一個稱為 mutable裝飾器 函式 ,亦即,一個會傳回函式的函式,且該函式是傳入函式的變形(包裝器)

function mutable(func)
  local currentfunc = func
  local function mutate(func, newfunc)
    local lastfunc = currentfunc
    currentfunc = function(...) return newfunc(lastfunc, ...) end
  end
  local wrapper = function(...) return currentfunc(...) end
  return wrapper, mutate
end

在這裡,函式包裝器提供一個原函式(currentfunc)的中介層,並允許作為上值的 currentfunc 識別碼進行變異。在變異期間,我們允許取代另一個函式的函式知道自己所取代函式的識別碼,從而允許連鎖效果,其中一個函式會覆寫或篩選前一個函式的行為。

範例用法

local sqrt, mutate = mutable(math.sqrt)
assert(sqrt(4) == 2)
assert(sqrt(-4) ~= sqrt(-4)) -- NaN
mutate(sqrt, function(old, x) return x < 0 and old(-x) .. "i" or old(x) end)
assert(sqrt(4) == 2)
assert(sqrt(-4) == "2i")

相對於使用以下方式,進行上述動作可能用處不大

local function sqrt(x) return x < 0 and math.sqrt(-x) .. "i" or math.sqrt(x) end

但以下是如何使用函式來模擬表格語意

local t, mutate = mutable(function() end)
mutate(t, function(old, x) if x == 1 then return "first" else return old(x) end end)
mutate(t, function(old, x) if x == 2 then return "second" else return old(x) end end)
mutate(t, function(old, x) if x == 3 then return "third" else return old(x) end end)
assert(t(1) == "first" and t(2) == "second" and t(3) == "third")

設定語法和效率當然有其不足,但我們獲得了更通用的語意

local t, mutate = mutable(function() end)
mutate(t, function(old, x,y) if x == 1 then return "first"
                                       else return old(x,y) end end)
mutate(t, function(old, x,y) if x == 2 then return "second"
                                       else return old(x,y) end end)
mutate(t, function(old, x,y) if x > 2  then return "large number"
                                       else return old(x,y) end end)
mutate(t, function(old, x,y) if y ~= 0 then return "off axis", math.sqrt(x^2+y^2)
                                       else return old(x,y) end end)
assert(t(1,0) == "first" and t(2,0) == "second" and t(5,0) == "large number" and
       t(3,4) == "off axis")
assert(select(2, t(3,4)) == 5)

我們現在具有後備的 metamethods 語意(例如 __index),以及索引和傳回多個值的技能,後者是我們之前在 Lua 表格中所沒有的。

讓我們使用包裝 mutate 的幾個輔助函式來整理語法

local SET = function() end    -- unique key
local MUTATE = function() end -- unique key

-- decorator function for adding methods.
function mutable_helpers(func, mutate)
  mutate(func, function(old_func, ...)
    if select(1, ...) == SET then
      local k = select(2, ...)
      local v = select(3, ...)
      mutate(func, function(old_func, ...)
        if select(1, ...) == k then return v
        else return old_func(...) end
      end)
    else
      return old_func(...)
    end
  end)
  mutate(func, function(old_func, ...)
    if select(1, ...) == MUTATE then
      local new_func = select(2, ...)
      mutate(func, function(old_func, ...)
        return new_func(old_func, ...)
      end)
    else
      return old_func(...)
    end  
  end)
  return func
end

mutable_helpers 是一個裝飾函式,可新增對可變函式表示物件的方法呼叫的語意支援。這些方法是 SET(用於設定表格值)和 MUTATEmutate 函式的變更語法)。SETMUTATE 是識別方法的唯一鍵。這些方法利用了函式為物件的事實,函式具有唯一的識別碼(在特殊情況下,字串可替代為鍵 - 例如 "set""mutate")。

因此,我們現在可以在訊息傳遞形式的方法呼叫中使用類似的方法來存取模擬表格

local t = mutable_helpers(mutable(function() end))
t(MUTATE, function(old, ...)
  local x = select(1, ...)
  if type(x) == "number" and x > 2 then return "large" else return old(...) end
end)
t(SET, 1, "first")
t(SET, 2, "second")
assert(t(1) == "first", t(2) == "second", t(5) == "large")

我們可以選擇性修改函式的預設元表格以使用一般的 Lua 表格語法。(這是一個會使用真實的 Lua 表格的唯一時機,但它僅是 Lua 元機制用於支援表格語法的一個成品,而透過對 Lua 執行一些修補,就可以避免它。)

-- Enable table get/set syntax.
-- Warning: uses debug interface
function enable_table_access()
  local mt = {
    __index    = function(t,k)   return t(k) end,
    __newindex = function(t,k,v) return t(SET, k, v) end,
  }
  debug.setmetatable(function() end, mt)
end

表格建構函式的輔助函式也將被定義

function T() return mutable_helpers(mutable(function() end)) end

範例用法

local t = T()
t[1] = "first"
t[2] = "second"
t[3] = "third"
assert(t[1] == "first" and t[2] == "second" and t[3] == "third" and t[4] == nil)

因此,在表達式方面,這表示表格不是一個 Lua 必要的功能,因此我們很可能能從語言中完全移除它們,儘管我們或許不會想因為效率考量而這麼做。

更實際的是,這或許說明了表和函數的概念可在程式語言中進一步統一,雖然為了效率,需要保留底層實作中的區別。

-- setting properties on an object
obj.color = "blue"
obj["color"] = "blue"
obj:size(10,20,30)        -- traditional syntax, method call style
obj("size") = (10,20,30)  -- multivalued setter syntax, function style
obj["size"] = (10,20,30)  -- multivalued setter syntax, table style
obj.size    = (10,20,30)  -- multivalued setter syntex, table property style
x,y = obj("position")     -- multivalued getter syntax, function style
x,y = obj["position"]     -- multivalued getter syntax, table style
x,y = obj.position        -- multivalued getter syntex, table property style 
obj[10,20] = 2            -- multivalued keys, table style
obj(10,20) = 2            -- multivalued keys, function style

相關討論:LuaList:2007-07/msg00177.html - "__index 元方法中有多個回傳值"

--DavidManura

另見


RecentChanges · 喜好設定
編輯 · 歷程記錄
最後編輯日期為 2009 年 5 月 2 日上午 2:19 GMT (diff)