Lua 技巧 |
|
技巧可能不如 Lua设计模式 优雅、实用和推荐。但是,它们仍然具有学术价值。它们可能导致更优雅的解决方案,或为其他问题提供有用的启发。
每个技巧都用一个名称标识,并包含对它试图做什么以及如何做的描述。
一些示例使用 getfenv 或 setfenv,这些函数在 Lua 5.1 之后被弃用。
以下示例使 Lua 的行为类似于 Perl 的 Exporter/import 机制,用于将模块变量导出到调用者命名空间。
-- file test.lua local mymodule = require("mymodule") assert(hello == nil) -- not imported mymodule.hello() local mymodule = require("mymodule"):import{"hello"} hello() assert(goodbye == nil) -- not imported assert(mymodule.print == nil) -- not visible require("mymodule"):import{":all"} goodbye()
-- file mymodule.lua -- Globals get collected by this table. local collector = {} setfenv(1, setmetatable(collector, {__index = _G})) -- Now define the module functions. function hello() print("hello?") end function goodbye() print("goodbye?") end -- Used to import functions into caller namespace -- (like in Perl's "import" function in Exporter.pm) function import(self, option) -- convert any option list to set local optionset = {} for _,v in ipairs(option) do optionset[v] = true end -- import selected variables into caller namespace local env = getfenv(2) for k,v in pairs(collector) do if optionset[":all"] or optionset[k] then env[k] = v end end return self end -- Return public module object (don't expose globals) local mymodule = {} for k,v in pairs(collector) do mymodule[k] = v end return mymodule
-- output hello? hello? goodbye?
注意:最好使用 mymodule = requre "mymodule"; local hello = mymodule.hello
之类的方法,它只将单个符号导入到您的命名空间(更简洁)。import{":all"} 具有类似的用途和 Java 的静态导入的缺点。
--DavidManura,2006-10,Lua 5.1
警告:以下内容属于学术性内容,不建议在大多数情况下使用。
在静态作用域流行之前,我们使用动态作用域([维基百科:作用域(编程)])。让我们在 Lua 中模拟它。
为此,我们可以创建一个函数来包装提供的函数,以便对某些变量应用动态作用域。
-- (func, varnames*) --> func function dynamic(func, ...) local varnames = {...} local saves = {} return function(...) for _,varname in ipairs(varnames) do saves[varname] = _G[varname] end local result = pack2(func(...)) for _,varname in ipairs(varnames) do _G[varname] = saves[varname] end return unpack2(result) end end
上面的函数使用了 VarargTheSecondClassCitizen 中“Vararg 保存”设计模式的 pack2
和 unpack2
函数。dynamic
在函数调用之前和之后保存和恢复给定全局变量的值,从而模拟动态变量。以下是一个使用示例
test2 = dynamic(function() print("test2:", x, y) x = 6 y = 7 return x, y end, 'y') test1 = dynamic(function() print("test1:", x, y) x = 4 y = 5 print("test2:", test2()) print("test1:", x, y) end, 'x') x = 2 y = 3 test1() -- Output: -- print("main:", x, y) -- test1: 2 3 -- test2: 4 5 -- test2: 6 7 -- test1: 6 5 -- main: 2 5
注意:支持 RAII 的语言([维基百科:资源获取即初始化])可以使用 RAII 实现这一点。可能有一些方法可以在 Lua 中模拟 RAII(LuaList:2006-09/msg00846.html)。
上面的代码没有正确处理引发异常的函数。这将需要在 dynamic
中插入一个 pcall
。
--DavidManura,2007-01
警告:以下内容属于学术性内容,不建议在大多数情况下使用。
在 Lua 中,如果你给一个未定义的变量赋值,该变量将被创建为全局变量而不是局部变量。让我们改变这一点,使变量默认情况下为局部变量(就像 Python 中一样 [2])。“局部”是指在最小的词法作用域中,而不是在 Lua 实现意义上的存储在 Lua 堆栈中(在这里,我们使用堆上的全局变量在内部实现局部变量)。
就像之前的“动态作用域变量”设计模式一样,我们创建了一个实用函数来包装我们想要为其分配此行为的其他函数,并使用 pack2
和 unpack2
函数
function localbydefault(func) local upenv = getfenv(2) local mt = {}; mt.__index = upenv return function(...) local env = setmetatable({}, mt) -- storage for locals local oldenv = getfenv(func) setfenv(func, env) local result = pack2(func(...)) setfenv(func, oldenv) return unpack2(result) end end
这会导致为每个函数调用创建一个用于局部变量的临时环境。
示例
test2 = localbydefault(function() print("test2:", x, y) x = 6; y = 7 _G.z = 8 return x, y end) test1 = localbydefault(function() print("test1:", x, y) x = 4; y = 5 print("test1:", x, y) print("test2:", test2()) print("test1:", x, y) localbydefault(function() -- nested print("test3:", x, y) x = 9; y = 10 print("test3:", x, y) end)() print("test1:", x, y) end) x = 2 test1() print("main:", x, y, z) -- Output: -- test1: 2 nil -- test1: 4 5 -- test2: 2 nil -- test2: 6 7 -- test1: 4 5 -- test3: 4 5 -- test3: 9 10 -- test1: 4 5 -- main: 2 nil 8
注意如何通过 _G
变量访问全局变量。
这种方法也适用于递归函数
fibonacci = localbydefault(function(n) if n == 1 then return 0 elseif n == 2 then return 1 else x1 = fibonacci(n - 1) x2 = fibonacci(n - 2) return x1 + x2 end end) assert(fibonacci(10) == 34)
上面的函数是使用临时变量编写的,如果这些临时变量是全局变量而不是局部变量,它将失败(例如,尝试从该函数中删除 localbydefault
)。但是,x1
和 x2
确实是词法作用域的局部变量,并且该函数有效。
-- David Manura,2007-01
//注释:什么是 pack2 和 unpack2
警告:以下内容属于学术性内容,不建议在大多数情况下使用。
C++ iostreams [3] 的使用方式如下
#include <iostream> using namespace std; int main() { cout << "hello" << "world" << 123 << endl; return 0; }
我们可以用 Lua 来模拟它。
cout = function (str) if str ~= nil then io.write(tostring(str), " ") else io.write("\n") end return cout end
示例用法
cout "hello" "world" (123) () --> "hello world 123\n"
你甚至可以添加粘性格式化函数。
cout "asdf" (intfmt(3)) (i)
另请参阅 SimpleStringBuffer 以获取相关示例。
警告:以下内容属于学术性内容,不建议在大多数情况下使用。
如 StringInterpolation 中所示,可以定义一个函数,该函数按词法范围访问其调用者中的变量。
local x = 3 assert(interp "x = ${x}" == "x = 3")
这是通过 debug.getlocal
完成的。
此方法的另一个应用可能是消除在 ShortAnonymousFunctions 中传递局部变量到“字符串化匿名函数”的需要。
此示例在运行时修改字节码。可能会有更多有用的技巧基于它。请注意,string.dump
不会保留上值,这限制了此方法的实用性(但另请参阅 PlutoLibrary)。
function add(x,y) return x + y end function test(x,y) print("here is waht the answer is...") print("teh answer is", add(x,y)) end local replacements = { ["teh"] = "the", ["waht"] = "what" } function fix_spelling() local env = getfenv(2) for k,v in pairs(env) do if type(v) == "function" then local success, bytes = pcall(function() return string.dump(v) end) if success then local is_changed, n = false, nil for k,v in pairs(replacements) do bytes, n = bytes:gsub(k, v) is_changed = is_changed or (n > 0) end if is_changed then env[k] = assert(loadstring(bytes)) end end end end end fix_spelling() test(2,3)
$ lua test.lua here is what the answer is... the answer is 5
--DavidManura,2007-03
警告:此示例仅供学术研究,不适合生产环境使用。
以下是我们如何创建一个代理表,该表可以对局部变量进行读写操作,并进行 get/set 操作。在内部,它使用 debug.getlocal
和 debug.setlocal
调用,这就是它成为技巧的原因。
-- Returns a proxy table representing all locals visible to the -- given stack level <level>. The table is readable -- and writable (writing modifies the local). -- -- If <level> is nil, the default used is 1, -- which indicates the stack level of the caller. -- -- NOTE: This function is based on debug.getlocal() -- and may be slow. do local Nil = {} -- placeholder (for storing nils in tables) local function getstackdepth(level) local n = 1 while debug.getinfo(n+level+1, "") ~= nil do n=n+1 end return n end function getlocals(level) level = (level or 1) + 1 -- Note: this correctly handles the case where two locals have the -- same name: "local x=1; local x=2 ... get_locals() ... local x=3". local mt = {} local proxy = setmetatable({}, mt) local levels = {} -- map: variable name --> stack level local indicies = {} -- map: variable name --> stack index local depth = getstackdepth(level) for k=1,depth do -- Determine number of locals (nlocs) -- Note: it would be easier if debug.getinfo returned nlocs. local nlocs = 0 while debug.getlocal(level, nlocs+1) do nlocs = nlocs + 1 end -- Record locations of locals by name. for n=nlocs,1,-1 do local lname, lvalue = debug.getlocal(level, n) if lvalue == nil then lvalue = Nil end -- placeholder if not levels[lname] then -- not shadowed levels[lname] = level indicies[lname] = n end end level = level + 1 end -- proxy handlers for read/write on table. function mt.__index(t, k) local depthdiff = getstackdepth(2) - depth if depthdiff < 0 then error("stack out of scope", 2) end local level = levels[k] local v if level then level = level + depthdiff -- a correction local _; _, v = debug.getlocal(level, indicies[k]) if v == Nil then v = nil end end return v end function mt.__newindex(t, k, v) local depthdiff = getstackdepth(2) - depth if depthdiff < 0 then error("stack out of scope", 2) end local level = levels[k] if level then level = level + depthdiff -- a correction debug.setlocal(level, indicies[k], v) end end -- note: "stack out of scope" could be made more robust (see test suite) return proxy end end -- test suite function test() local function update(L) assert(L.x == 10) L.x = 20 end local L2 local x = 1 local y = 3 local z = 5 function f1() local x = 2 local y = nil local x = 4 local _L = getlocals() assert(_L.w == nil) assert(_L.x == 4) assert(_L.y == nil) assert(_L.z == 5) _L.z = 6 -- modify local through table! assert(z == 6 and _L.z == 6) z = 7 assert(z == 7 and _L.z == 7) _L.x = 10 assert(x == 10 and _L.x == 10) update(_L) assert(x == 20 and _L.x == 20) L2 = _L local x = 5 -- doesn't interfere end f1() -- Note: L2 is invalid at this scope. -- print(L2.x) -- will correctly raise an error -- L2.x = 1 -- will correctly raise an error -- update(L2) -- opps, this doesn't trigger "stack out of scope" print "done" end test()
-- 我没有编写上面的内容,我是一个完全不同的人--
我真的很喜欢这个页面。我读了这些内容试图理解它们。我不理解这个标题或代码,所以我决定跳过它。现在,最近我做了与这段代码完全相同的事情,但更短,更高效。
使用这段代码,_L 始终存在,它不必用 '_L = getlocals()' 重复定义,它也是可读写的。它更容易理解,不像上面的代码那样过于复杂。代码如下;
--Setup-- setlocal = function(lvl, name, val) local setlocal = saved if lvl ~= 0 then lvl = lvl + 1 end local i = 1 while true do local var, value = debug.getlocal(lvl, i) if not var then break end if var == name then debug.setlocal(lvl, i, val) end i = i + 1 end end --End of setup-- --Code-- setmetatable(_G, { __index = function(tab, var, val) if var == "_L" then local variables = {} local idx = 1 while true do local ln, lv = debug.getlocal(2, idx) if ln ~= nil then variables[ln] = lv else break end idx = 1 + idx end return setmetatable({}, { __index = variables, __newindex = function(tab, var, val) rawset(variables, var, val) setlocal(2, var, val) end }) end end }) --End of code-- --Tests-- local a = 1 print(a) _L.a = 5 print(a) print(_L.a) --End of tests--
我觉得我应该感谢 stackoverflow 上的 u0b34a0f6ae 提供了 setlocal 函数的一部分,他在此处发布了它;"http://stackoverflow.com/questions/2834579/print-all-local-variables-accessible-to-the-current-scope-in-lua"(它被标记为最佳答案)
我目前正在尝试为 _L 实现一个元表,如果你有任何想法,请在下面评论。
以下模拟了用户定义的控制结构和关键字,允许一定程度的元编程。(另请参阅 RuntimeSyntax。)
local Expr = {} local function ev(o) -- terminate and eval expression if getmetatable(o) == Expr then return o.END() else return o end end function Expr.__unm(a) return -ev(a) end function Expr.__len(a) return #ev(a) end function Expr.__add(a, b) return ev(a) + ev(b) end function Expr.__sub(a, b) return ev(a) - ev(b) end function Expr.__mul(a, b) return ev(a) * ev(b) end function Expr.__div(a, b) return ev(a) / ev(b) end function Expr.__pow(a, b) return ev(a) ^ ev(b) end function Expr.__concat(a,b) return ev(a) .. ev(b) end function Expr.__eq(a,b) return ev(a) == ev(b) end function Expr.__lt(a,b) return ev(a) < ev(b) end function Expr.__le(a,b) return ev(a) <= ev(b) end function Expr.__index(a,b) return ev(a)[ev(b)] end --function Expr.__newindex(a,b,c) ev(a)[ev(b)] = ev(c) end function Expr.__call(o, ...) if ... == ';' then return o.END() elseif o.__CALL then return o.__CALL(...) else return ev(a)(...) end end function Expr:clear() for k,v in pairs(self) do self[k] = nil end end local function eval(t) if type(t) == "function" or getmetatable(t) == Expr then return t() end local s = "" local ts = {} local vals = {} if type(t) == "table" then for k,v in pairs(t) do if type(k) ~= "number" then vals[#ts+1] = v ts[#ts+1] = k end end t = t[1] s = ((#ts > 0) and "local " .. table.concat(ts, ",") .. " = ...; " or "") end local s = s .. "return " .. t local f = loadstring(s) return f(unpack(vals, 1, #ts)) end --print(eval {[[x+y]], x = 2, y = 5}) function Expr.EVAL (expr) return eval(expr) end function Expr.IF (cond) local o = setmetatable({}, Expr) function o.THEN (stat) o:clear() function o.END () if (eval(cond)) then eval(stat) end end o.__CALL = o.END return o end; return o end function Expr.LET (name) local o = setmetatable({}, Expr) function o.EQUAL (expr) o:clear() function o.END () _G[name] = eval(expr) end function o.IN (expr2) o:clear(); function o.END() local oldval = _G[name] _G[name] = eval(expr) local function helper(...) _G[name] = oldval return ... end return helper(eval(expr2)) end; return o end; return o end o.__CALL = o.EQUAL return o end function Expr.FOR (var) local o = setmetatable({}, Expr) function o.FROM (i1) o:clear(); function o.TO (i2) o:clear(); function o.DO (expr) o:clear(); function o.END() for n=eval(i1),eval(i2) do _G[var] = n eval(expr) end end; return o end; return o end; return o end return o end Expr.__index = Expr setmetatable(_G, Expr) -- TEST LET 'x' .EQUAL '1' ';' LET 'y' .EQUAL '3' ';' IF 'x == 1' .THEN 'print(x+2)' ';' FOR 'n' .FROM '1' .TO '3' .DO ( IF 'n > 1' .THEN 'print(n,x)' ) ';' print(1 + (LET 'x' '2' .IN 'x*y') + 1) print(EVAL 'x') --[[OUTPUT: 3 2 1 3 1 8 1 --]]
-- DavidManura,2007-07
Ruby 具有名为 Symbols 的功能,它使用特殊语法实现字符串驻留。在 Ruby 中,使用符号作为哈希表的键是一种常见做法。由于 Lua 默认情况下已经执行字符串驻留,因此此技巧仅供学术研究。它通过滥用 __index 元方法来实现,该方法使用一个函数填充,该函数仅返回查询它的键作为值。
S=setmetatable({},{__index=function(S,k) return k end}) print( S.Symbol_Name )
此技巧的更实用的表达方式是作为通过函数创建枚举表的替代方法。字符串被缓存以提高性能。此外,它已被格式化为可读性。
Enum = setmetatable( {}, { __index=function(E, k) E[k] = k return k end}) print( Enum.Apple, Enum.Banana, Enum.Carrot )
类似于滥用 Ruby 风格符号的 __index 元方法,我们可以跳过在加载模块时用引号括住模块名称的步骤。这个技巧通过点符号构建字符串,因为模块可能隐藏在目录树的深处。任意限制是目录结构假设第一个子目录。require 函数也需要被包装,因为它不会尝试对传入的表进行 tostring 操作。
do local wrap_require = require require = function(t) wrap_require(tostring(t)) end local mt = {} function mt:__index(key) return setmetatable({path=rawget(self, "path").."."..key}, mt) end function mt:__tostring() return rawget(self, "path") or "" end lib = setmetatable({path="lib"},mt) end do require(lib.foo.bar)
在 Lua 5.2 中,setfenv 和 getfenv 都被弃用并删除了。虽然许多人更喜欢动态环境而不是词法环境。这个想法是环境不应该在它的函数之外被访问。尽管本页上的许多技巧都使用了它们。它们非常适合测试目的,但并不推荐在常规使用中使用,也不推荐作为程序的主要部分,因为它使用了调试库,这违反了一些通用的 Lua 规则,并且可能很慢。
我没有编写这些代码,我在网上找到了它们,我认为它们应该放在这里,一起。我认为它们都发布在 stackoverflow 上,快速谷歌搜索就能找到作者。以下是代码
--Setup function setfenv(f, t) f = (type(f) == 'function' and f or debug.getinfo(f + 1, 'f').func) local name local up = 0 repeat up = up + 1 name = debug.getupvalue(f, up) until name == '_ENV' or name == nil if name then debug.upvaluejoin(f, up, function() return t end, 1) end end function getfenv(f) f = (type(f) == 'function' and f or debug.getinfo(f + 1, 'f').func) local name, val local up = 0 repeat up = up + 1 name, val = debug.getupvalue(f, up) until name == '_ENV' or name == nil return val end --Tests function x() print(hi) end x() --nil print(hi) --nil setfenv(x, {print = print, hi = 5}) x() --5 print(hi) --nil for i,v in pairs(getfenv(x)) do print(i, v) end --print function address --hi 5 setfenv(x, {}) x() --attempt to call global 'print' (a nil value)