[英]Composing two functions in lua
我剛開始學習 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 ("called 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(...))
正如評論中提到的,要將兩個函數鏈接在一起,您可以執行以下操作:
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
上面的check_functions
助手只是檢查傳入的東西確實是函數——如果不是,則會引發錯誤。 為簡潔起見省略了實現。
+ :合理直接的方法。 可能是你第一次嘗試時想到的。
- : 資源效率不高。 很多垃圾表來存儲調用之間的結果。 您還必須處理打包和解包結果。
這里的關鍵見解是,即使我們調用的函數不是遞歸的,也可以通過在遞歸函數上搭載它來使其遞歸。
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
loadstring(luacode)()(...)
可能需要一些解釋。 這里我選擇在生成的腳本中將函數鏈編碼為參數名稱( P1, P2, P3
等)。 額外的()
括號用於“解開”嵌套函數,因此最里面的函數是返回的函數。 P1, P2, P3 ... Pn
參數成為鏈中每個函數的捕獲上值,例如。
function(...)
return P1(P2(P3(...)))
end
請注意,您也可以使用setfenv
完成此操作,但我選擇這條路線只是為了避免 lua 5.1 和 5.2 之間關於如何設置函數環境的重大更改。
+ :避免額外的中間表,如方法#2。 不濫用堆棧。
- :需要額外的字節碼編譯步驟。
您可以遍歷傳遞的函數,使用前一個函數的結果連續調用鏈中的下一個函數。
function module._compose(...)
local n = select('#', ...)
local args = { n = n, ... }
local currFn = nil
for _, nextFn in ipairs(args) do
if type(nextFn) == 'function' then
if currFn == nil then
currFn = nextFn
else
currFn = (function(prev, next)
return function(...)
return next(prev(...))
end
end)(currFn, nextFn)
end
end
end
return currFn
end
注意上面的立即調用函數表達式的使用,它允許重用的函數變量不會調用無限遞歸循環,這發生在以下代碼中:
function module._compose(...)
local n = select('#', ...)
local args = { n = n, ... }
local currFn = nil
for _, nextFn in ipairs(args) do
if type(nextFn) == 'function' then
if currFn == nil then
currFn = nextFn
else
currFn = function(...)
return nextFn(currFn(...)) -- this will loop forever, due to closure
end
end
end
end
return currFn
end
雖然 Lua 不支持三元運算符,但可以使用短路求值來移除內部的if
語句:
function module.compose(...)
local n = select('#', ...)
local args = { n = n, ... }
local currFn = nil
for _, nextFn in ipairs(args) do
if type(nextFn) == 'function' then
currFn = currFn and (function(prev, next)
return function(...)
return next(prev(...))
end
end)(currFn, nextFn) or nextFn
end
end
return currFn
end
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.