在lua中组合两个函数

Nik*_*vic 3 lua function

我刚开始学习lua,所以我的要求可能是不可能的。

现在,我有一个接受函数的方法:

function adjust_focused_window(fn)
  local win = window.focusedwindow()
  local winframe = win:frame()
  local screenrect = win:screen():frame()
  local f, s = fn(winframe, screenrect)
  win:setframe(f)
end
Run Code Online (Sandbox Code Playgroud)

我有几个接受这些框架和矩形的函数(仅显示一个):

function full_height(winframe, screenrect)
   print ("called full_height for " .. tostring(winframe))
  local f = {
     x = winframe.x,
     y = screenrect.y,
     w = winframe.w,
     h = screenrect.h,
  }
  return f, screenrect
end
Run Code Online (Sandbox Code Playgroud)

然后,我可以执行以下操作:

hotkey.bind(scmdalt, '-', function() adjust_focused_window(full_width) end)
Run Code Online (Sandbox Code Playgroud)

现在,我如何adjust_focused_window在不更改其定义的情况下将多个函数组合为 。就像是:

hotkey.bind(scmdalt, '=', function() adjust_focused_window(compose(full_width, full_height)) end)
Run Code Online (Sandbox Code Playgroud)

where将返回一个接受与和compose2相同参数的函数,并在内部执行类似以下操作:full_widthfull_height

full_height(full_width(...))
Run Code Online (Sandbox Code Playgroud)

gre*_*olf 5

正如评论中提到的,要将两个函数链接在一起,您可以这样做:

function compose(f1, f2)
  return function(...) return f1(f2(...)) end
end
Run Code Online (Sandbox Code Playgroud)

但是如果您想将 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
Run Code Online (Sandbox Code Playgroud)

上面的助手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
Run Code Online (Sandbox Code Playgroud)

+:不会像上面那样创建额外的临时表。精心编写为尾递归——这意味着调用长函数链不需要额外的堆栈空间。它有一定的优雅。

元脚本生成

通过最后一种方法,您使用一个 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
Run Code Online (Sandbox Code Playgroud)

可能loadstring(luacode)()(...)需要一些解释。这里我选择P1, P2, P3在生成的脚本中将函数链编码为参数名称(等)。额外的()括号用于“展开”嵌套函数,因此最内部的函数就是返回的函数。这些P1, P2, P3 ... Pn参数成为链中每个函数捕获的上值,例如。

function(...)
  return P1(P2(P3(...)))
end
Run Code Online (Sandbox Code Playgroud)

请注意,您也可以使用以下方法来完成此操作setfenv,但我选择此路线只是为了避免 lua 5.1 和 5.2 之间关于如何设置函数环境的重大更改。

+:避免额外的中间表,如方法#2。不滥用堆栈。

-:需要额外的字节码编译步骤。