可变函数

lua-users home
wiki

作为一等公民的对象实现的函数和闭包可以实现各种各样的语义,正如我们在函数式编程中所了解的那样。这里展示了如何使用闭包来实现 Lua 表和对象的语义,而不使用任何 Lua 表。

我们首先创建一个名为 mutable装饰器函数 [1],也就是说,它返回一个函数,该函数是传入函数的变体(包装器)。

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)

我们现在拥有了元方法(例如 __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 - "Multiple return value in __index metamethod"

--DavidManura

另请参阅


RecentChanges · preferences
编辑 · 历史
最后编辑于 2009年5月1日 晚上8:19 GMT (差异)