在lua中组合两个函数 [英] Composing two functions in lua

查看:142
本文介绍了在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

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

  function full_height(winframe,screenrect)
print(full_height for.. tostring(winframe))
local f = {
x = winframe.x,
y = screenrect.y,
w = winframe.w,
h = screenrect.h,
}
return f,screenrect
end

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

  hotkey.bind(scmdalt,' - ',function()adjust_focused_window(full_width)end)

现在,我怎样才能将 adjust_focused_window ,而不改变它的定义。例如:

  hotkey.bind(scmdalt,'=',function()adjust_focused_window(compose(full_width,full_height))end )

其中 compose2 会返回一个函数接受与 full_width full_height 相同的参数,并在内部执行如下操作:

  full_height(full_width(...))


解决方案

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

  







$ pre>

但是如果你想连接两个以上的函数呢?你可能会问,是否可以一起构成任意数量的函数?

答案是肯定的 - 下面我将展示3种不同的方法来实现这个再加上一个快速总结它们的结果。

迭代表方法



这里的想法是调用每个函数一个接一个地依次列出。在这样做的过程中,您将前一次调用的返回结果保存到一个表中,然后解压该表并将其传递到下一次调用中。

 <$ c (...)
local fnchain = check_functions {...}
返回函数(...)
本地args = {...}
for _,fn in ipairs(fnchain)do
args = {fn(unpack(args))}
end
return unpack(args)
end
end

上面的 check_functions helper检查是否有东西传入的确是函数 - 如果不是,则会引发错误。为简洁起见,省略实施。

+ :合理直接的方法。可能是第一次尝试时想出来的结果。



- :对资源效率不高。很多垃圾表在调用之间存储结果。您还必须处理打包和解包结果。



Y-Combinator模式



这里的关键洞察即使我们调用的函数不是递归的,也可以通过在递归函数中捎带它进行递归。

 函数compose2(...)
本地fnchain = check_functions {...}
本地函数recurse(i,...)
if == == #fnchain然后返回fnchain [i](...)end
return recurse(i + 1,fnchain [i](...))
end
return function(...)return recurse( 1,...)end
end

+ :不会像上面那样创建额外的临时表。仔细写成尾递归 - 这意味着调用长功能链时不需要额外的堆栈空间。

元代码生成



使用最后一种方法,您可以使用lua函数实际上会生成执行所需函数调用链的精确lua代码。

 函数compose3(...)
本地luacode =
[[
返回函数(%s)
返回函数(...)
返回%s
结束
结束
$]
local paramtable = {}
local fcount = select('#',...)
for i = 1,fcount do
table.insert(paramtable ,P.. i)
end
local paramcode = table.concat(paramtable,,)
本地callcode = table.concat(paramtable,()..
(...).. string.rep(')',fcount - 1)
luacode = luacode:format(paramcode,callcode)
return loadstring(luacode)()( ...)
end

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

  function(...)
return P1(P2(P3(...)))
end

请注意,您也可以使用 setfenv 来完成此操作,但我选择了此路线以避免打破了lua 5.1和5.2之间关于函数环境设置的变化。



+ :避免像方法#2那样的额外中间表。不要滥用堆栈。



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


I just started learning lua, so what I'm asking might be impossible.

Now, I have a method that accepts a function:

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

I have several functions that accept these frames and rectangles (showing just one):

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

Then, I can do the following:

hotkey.bind(scmdalt, '-', function() adjust_focused_window(full_width) end)

Now, how could I compose several functions to adjust_focused_window, without changing it's definition. Something like:

hotkey.bind(scmdalt, '=', function() adjust_focused_window(compose(full_width, full_height)) end)

where compose2 would return a function that accepts the same parameters as full_width and full_height, and internally does something like:

full_height(full_width(...))

解决方案

As mentioned in the comments, to chain two functions together you can just do:

function compose(f1, f2)
  return function(...) return f1(f2(...)) end
end

But what if you want to connect more than 2 functions together? You might ask, is it possible to 'compose' an arbitrary number of functions together?

The answer is a definite yes -- below I show 3 different approaches for implementing this plus a quick summary of their consequences.

Iterative Table approach

The idea here is to call each function in the list one after the other in turn. While doing so, you save the returned results from the previous call into a table and you unpack that table and pass it into the next call.

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 helper above just checks that the stuff passed in are indeed functions -- raises an error if not. Implementation omitted for brevity.

+: Reasonably straight-forward approach. Probably what you'd come up with on a first attempt.

-: Not very efficient on resources. A lot of garbage tables to store results between calls. You also have to deal with packing and unpacking the results.

Y-Combinator Pattern

The key insight here is that even though the functions we're calling isn't recursive, it can be made recursive by piggy-backing it on a recursive function.

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

+: Doesn't create extra temporary tables like above. Carefully written to be tail-recursive -- that means no extra stack space needed for calls to long function chains. There's a certain elegance to it.

Meta-script generation

With this last approach, you use a lua function that actually generates the exact lua code that performs the function call chain desired.

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)()(...) probably needs some explaining. Here I chose to encode the function chain as parameter names (P1, P2, P3 etc.) in the generated script. The extra () parenthesis is there to 'unwrap' the nested functions so the inner most function is what's returned. The P1, P2, P3 ... Pn parameters become captured upvalues for each of the functions in the chain eg.

function(...)
  return P1(P2(P3(...)))
end

Note, you could also have done this using setfenv but I chose this route just to avoid the breaking change between lua 5.1 and 5.2 on how function environments are set.

+: Avoids extra intermediate tables like approach #2. Doesn't abuse the stack.

-: Needs an extra byte-code compile step.

这篇关于在lua中组合两个函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆