可变函数 |
|
我们首先创建一个名为 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(用于设置表值)和 MUTATE(mutate 函数的替代语法)。SET 和 MUTATE 是标识这些方法的唯一键。它们利用了函数是对象这一事实,函数具有唯一的身份(在特殊情况下,可以使用字符串作为键,例如 "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"