正如评论中提到的,要将两个函数链接在一起,您可以这样做:
function compose(f1, f2)
return function(...) return f1(f2(...)) end
end
但是如果您想将 2 个以上的函数连接在一起怎么办?您可能会问,是否可以将任意数量的函数“组合”在一起?
答案是肯定的——下面我展示了实现这一点的 3 种不同方法以及对其后果的快速总结。
迭代表方法
这里的想法是依次调用列表中的每个函数。执行此操作时,您将上次调用返回的结果保存到一个表中,然后解压该表并将其传递到下一个调用中。
function compose1(...)
local fnchain = check_functions {...}
return function(...)
local args = {...}
for _, fn in ipairs(fnchain) do
args = {fn(unpack(args))}
end
return unpack(args)
end
end
The check_functions
上面的助手只是检查传入的内容是否确实是函数——如果不是,则会引发错误。为简洁起见,省略了实现。
+: 相当直接的方法。可能是您第一次尝试时想到的。
-: 资源效率不高。有很多垃圾表来存储调用之间的结果。您还必须处理打包和解包结果。
Y 组合器模式
这里的关键见解是,即使我们调用的函数不是递归的,也可以通过在递归函数上搭载它来使其递归。
function compose2(...)
local fnchain = check_functions {...}
local function recurse(i, ...)
if i == #fnchain then return fnchain[i](...) end
return recurse(i + 1, fnchain[i](...))
end
return function(...) return recurse(1, ...) end
end
+:不会像上面那样创建额外的临时表。精心编写为尾递归——这意味着调用长函数链不需要额外的堆栈空间。它有一定的优雅。
元脚本生成
通过最后一种方法,您使用一个 lua 函数,该函数实际上生成执行所需函数调用链的精确 lua 代码。
function compose3(...)
local luacode =
[[
return function(%s)
return function(...)
return %s
end
end
]]
local paramtable = {}
local fcount = select('#', ...)
for i = 1, fcount do
table.insert(paramtable, "P" .. i)
end
local paramcode = table.concat(paramtable, ",")
local callcode = table.concat(paramtable, "(") ..
"(...)" .. string.rep(')', fcount - 1)
luacode = luacode:format(paramcode, callcode)
return loadstring(luacode)()(...)
end
The loadstring(luacode)()(...)
可能需要一些解释。这里我选择将函数链编码为参数名称(P1, P2, P3
等)在生成的脚本中。额外的()
括号用于“展开”嵌套函数,因此最内部的函数就是返回的函数。这P1, P2, P3 ... Pn
参数成为链中每个函数捕获的上值,例如。
function(...)
return P1(P2(P3(...)))
end
请注意,您也可以使用以下方法来完成此操作setfenv
但我选择这条路线只是为了避免 lua 5.1 和 5.2 之间关于如何设置函数环境的重大变化。
+:避免额外的中间表,如方法#2。不滥用堆栈。
-:需要额外的字节码编译步骤。