可变函数

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 - "__index 元方法中的多个返回值"

--DavidManura

另请参阅


最近更改 · 偏好设置
编辑 · 历史记录
最后编辑于 2009 年 5 月 2 日凌晨 2:19 GMT (差异)