如果我想编写一个接受可变参数并返回一个接受可变参数的函数,我会遇到模棱两可的 ...
,例如
function bind(func, ...) return function(...) func(..., ...) end end
如果我想编写一个接受可变参数并返回一个接受可变参数的函数,我会遇到模棱两可的 ...
,例如
function bind(func, ...) return function(...) func(..., ...) end end
首先,你错过了关闭绑定函数的结尾。
如果你有歧义,只需使用不同的名称来解决它们。
function bind(func, ...)
return function(...) func(..., ...) end
end
bind(print, "a", "b", "c")(1,2,3)
,你会得到以下输出:
如果在函数参数列表中有1 1 2 3
...
或任何其他名称,该变量将成为该函数范围内的本地变量。它将优先于上层作用域中同名的任何其他变量。因此,匿名函数中的...
与bind
函数中的...
没有关系。function bind(func, ...)
local a = table.pack(...)
return function(...) func(table.unpack(a, 1, a.n), ...) end
end
a 1 2 3
要了解b和c发生了什么,请阅读此部分:https://www.lua.org/manual/5.3/manual.html#3.4.11
(当然还有Lua手册的其余部分)
当调用函数时,参数列表将根据参数列表的长度进行调整,除非该函数是vararg函数,这由其参数列表末尾的三个点(“...”)表示。 vararg函数不会调整其参数列表; 相反,它收集所有额外的参数并通过vararg表达式提供给函数,该表达式也写为三个点。此表达式的值是所有实际额外参数的列表,类似于具有多个结果的函数。如果vararg表达式用于另一个表达式内部或在表达式列表的中间,则其返回列表将调整为一个元素。如果该表达式用作表达式列表的最后一个元素,则不进行调整(除非该最后一个表达式括在括号中)。
因此,像func(...,...)这样的东西永远不起作用,即使...是两个不同的列表。
为了避免这种情况,您必须将两个参数列表连接起来。
function bind(func, ...)
local args1 = table.pack(...)
return function(...)
local args2 = table.pack(...)
for i = 1, args2.n do
args1[args1.n+i] = args2[i]
end
args1.n = args1.n + args2.n
func(table.unpack(args1, 1, args1.n))
end
end
bind(print, "a", nil, "c")(1,nil,3)
这将最终给我们所需的输出:
a nil c 1 nil 3
但我相信你可以想到更好的方法来实现你的目标,而不是连接各种可变参数。
nil
。va = require "vararg"
function bind(func, ...)
local args = va(...)
return function(...)
func(va.concat(args, va(...)))
end
end
bind(print, 1, nil, 2)(3, nil, 5)
这种方法类似于Rici Lake的Partial,它使用记忆化辅助函数而不是表格打包/解包。它具有大约2倍的性能优势,并且具有较低的内存使用率。
local fmt, cat, pack = string.format, table.concat, table.pack
local function args(n,Prefix)
local as,prefix = {}, Prefix or '_'
local step,from,to = n<0 and -1 or 1, n<0 and -n or 1, n<0 and 1 or n
for i=from,to,step do as[1+#as]=prefix..i end
return function(sep) return cat(as,sep or ',')end
end
local function paramsCat(...)
local r,p = {}, pack(...)
for i=1,p.n do if p[i]:len()>0 then r[1+#r]=p[i] end end
return cat(r,',')
end
local bind = setmetatable({}, {
__call = function(self,f,...)
local narg = select("#",...)
if not self[narg] then
local a = args(narg)()
local b = '...'
local src = fmt([[
return function(%s) -- _1,_2
return function(fn)
return function(...)
return fn(%s) -- _1,_2,...
end
end
end]],a, paramsCat(a,b))
local fn = load(src,'_')()
self[narg] = fn
end
return self[narg](...)(f)
end
})
稍作修改,我们可以将绑定扩展到以第n个参数开始的位置。
local bindn = setmetatable({}, {
__call = function(self, n, f, ...)
local narg = select("#",...)
if type(n)~='number' then -- shifted, n is the function now
if f~=nil or narg>0 then
return self(0, n, f, ...)
else
return self(0, n)
end
end
self[n] = self[n] or {}
if not self[n][narg] then
local a = args(n)()
local b = args(narg,'b')()
local c = '...'
local src = fmt([[
return function(%s) -- b1,b2
return function(fn)
return function(%s) -- _1,_2,_3,...
return fn(%s) -- _1,_2,_3,b1,b2,...
end
end
end]],b, paramsCat(a,c), paramsCat(a,b,c))
local fn = load(src,'_')()
self[n][narg] = fn
end
return self[n][narg](...)(f)
end
})
local dp = bindn(2,print,'debug-','print:')
dp(1,2,3,4,5) --> 1 2 debug- print: 3 4 5
dp = bindn(print,'debug-','print:')
dp(1,2,3,4,5) --> debug- print: 1 2 3 4 5
string.Bytes = bindn(1,string.byte,1,-1)
print(("Test"):Bytes()) --> 84 101 115 116