簡體   English   中英

在lua中組合兩個函數

[英]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_widthfull_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助手只是檢查傳入的東西確實是函數——如果不是,則會引發錯誤。 為簡潔起見省略了實現。

+ :合理直接的方法。 可能是你第一次嘗試時想到的。

- : 資源效率不高。 很多垃圾表來存儲調用之間的結果。 您還必須處理打包和解包結果。

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

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM