可变参数函数返回可变参数lambda表达式 Lua

4

如果我想编写一个接受可变参数并返回一个接受可变参数的函数,我会遇到模棱两可的 ...,例如

    function bind(func, ...) return function(...)  func(..., ...) end end
3个回答

5

首先,你错过了关闭绑定函数的结尾。

如果你有歧义,只需使用不同的名称来解决它们。

function bind(func, ...)
  return function(...)  func(..., ...) end
end

如果我们这样测试你的代码:bind(print, "a", "b", "c")(1,2,3),你会得到以下输出:

1 1 2 3

如果在函数参数列表中有...或任何其他名称,该变量将成为该函数范围内的本地变量。它将优先于上层作用域中同名的任何其他变量。因此,匿名函数中的...bind函数中的...没有关系。
要解决这个问题,你可以简单地执行类似以下的操作:
function bind(func, ...)
  local a = table.pack(...)
  return function(...)  func(table.unpack(a, 1, a.n), ...) end
end

现在调用bind(print,"a","b","c")(1,2,3)将输出:

a 1 2 3

要了解b和c发生了什么,请阅读此部分:https://www.lua.org/manual/5.3/manual.html#3.4.11

(当然还有Lua手册的其余部分)

当调用函数时,参数列表将根据参数列表的长度进行调整,除非该函数是vararg函数,这由其参数列表末尾的三个点(“...”)表示。 vararg函数不会调整其参数列表; 相反,它收集所有额外的参数并通过vararg表达式提供给函数,该表达式也写为三个点。此表达式的值是所有实际额外参数的列表,类似于具有多个结果的函数。如果vararg表达式用于另一个表达式内部或在表达式列表的中间,则其返回列表将调整为一个元素。如果该表达式用作表达式列表的最后一个元素,则不进行调整(除非该最后一个表达式括在括号中)。

因此,像func(...,...)这样的东西永远不起作用,即使...是两个不同的列表。

为了避免这种情况,您必须将两个参数列表连接起来。

 function bind(func, ...)
  local args1 = table.pack(...)
  return function(...)
      local args2 = table.pack(...)

      for i = 1, args2.n do
          args1[args1.n+i] = args2[i]
      end
      args1.n = args1.n + args2.n

      func(table.unpack(args1, 1, args1.n))
  end
end

bind(print, "a", nil, "c")(1,nil,3)

这将最终给我们所需的输出:

a nil c 1 nil 3

但我相信你可以想到更好的方法来实现你的目标,而不是连接各种可变参数。


你的变量在参数列表中无法处理 nil。 - moteus
@moteus 是的,我漏掉了那个,谢谢你的提示。已经修复了。 - Piglet

2
你可以尝试使用vararg库。它还可以处理参数中的nil
va = require "vararg"

function bind(func, ...)
  local args = va(...)
  return function(...)
      func(va.concat(args, va(...)))
  end
end

bind(print, 1, nil, 2)(3, nil, 5)

0

这种方法类似于Rici Lake的Partial,它使用记忆化辅助函数而不是表格打包/解包。它具有大约2倍的性能优势,并且具有较低的内存使用率。

local fmt, cat, pack = string.format, table.concat, table.pack

local function args(n,Prefix)  
  local as,prefix = {}, Prefix or '_'
  local step,from,to = n<0 and -1 or 1, n<0 and -n or 1, n<0 and 1 or n
  for i=from,to,step do as[1+#as]=prefix..i end
  return function(sep) return cat(as,sep or ',')end
end

local function paramsCat(...)
  local r,p = {}, pack(...)
  for i=1,p.n do if p[i]:len()>0 then r[1+#r]=p[i] end end
  return cat(r,',')
end

local bind = setmetatable({}, {
  __call = function(self,f,...)
    local narg = select("#",...)
    if not self[narg] then
      local a = args(narg)()
      local b = '...'
      local src = fmt([[
return function(%s) -- _1,_2       
  return function(fn)
    return function(...)
      return fn(%s) -- _1,_2,...
    end
  end
end]],a, paramsCat(a,b))
      local fn = load(src,'_')()
      self[narg] = fn
    end
    return self[narg](...)(f)
  end  
})

稍作修改,我们可以将绑定扩展到以第n个参数开始的位置。

local bindn = setmetatable({}, {
  __call = function(self, n, f, ...)
    local narg = select("#",...)
    if type(n)~='number' then -- shifted, n is the function now
      if f~=nil or narg>0 then
        return self(0, n, f, ...)
      else
        return self(0, n)
      end      
    end
    self[n] = self[n] or {}
    if not self[n][narg] then
      local a = args(n)()
      local b = args(narg,'b')()
      local c = '...'
      local src = fmt([[
return function(%s)     -- b1,b2
  return function(fn)
    return function(%s) -- _1,_2,_3,...
      return fn(%s)     -- _1,_2,_3,b1,b2,...
    end
  end
end]],b, paramsCat(a,c), paramsCat(a,b,c))
      local fn = load(src,'_')()
      self[n][narg] = fn
    end
    return self[n][narg](...)(f)
  end  
})

local dp = bindn(2,print,'debug-','print:')
dp(1,2,3,4,5) --> 1 2   debug-  print:  3   4   5
dp = bindn(print,'debug-','print:')
dp(1,2,3,4,5) --> debug-    print:  1   2   3   4   5

string.Bytes = bindn(1,string.byte,1,-1)
print(("Test"):Bytes()) --> 84  101 115 116

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接