Varags functions returning vargas lambdas lua

If I want to write a function that takes varargs and returns a function that takes vargas, I run an ambiguous ...

for example

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

      

+3


source to share


3 answers


First of all, you are missing the end to close the binding function.

If you have any ambiguities, just resolve them by using different names.

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

      

If we test your code like this: bind(print, "a", "b", "c")(1,2,3)

you get the result:

1 1 2 3

If you have ... or any other name in the parameter list of functions, this variable will be local within that function. It will take precedence over any other variable of the same name in excellent scope. So ... in your anonymous function has nothing to do with ... the bind function.

To fix this problem, you can simply do something like

function bind(func, ...)
  local a = table.pack(...)
  return function(...)  func(table.unpack(a, 1, a.n), ...) end
end

      

Now bind (print, "a", "b", "c") (1,2,3) is called:

a 1 2 3



To find out what happened to b and c, read this section: https://www.lua.org/manual/5.3/manual.html#3.4.11

(and of course the rest of the Lua manual)

When the function is called, the argument list is set to the length of the parameter list, unless the function is a vararg function, denoted by three dots ("...") at the end of the parameter list. The vararg function does not update the argument list; instead, it collects all additional arguments and supplies them to the function via a vararg expression, which is also written as three dots. The value of this expression is a list of all the actual optional arguments, similar to a function with multiple results. If a vararg expression is used within another expression or in the middle of a list of expressions, then its returned list is set to one element... If the expression is used as the last element of the list of expressions, then no adjustment is made (if this last expression is enclosed in parentheses).

So something like func (..., ...) would never work, even if ... there were two different lists.

To avoid this, you need to match both argument lists.

 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)

Which finally gives us the desired output:

a nil c 1 nil 3

But I'm sure you can think of a better way to achieve your goal without concatenating various varargs.

+4


source


You can try the vararg library. Which also handles nil

in arguments.



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)

      

+2


source


This approach is similar to Rici Lake Partial , which uses memoized helper functions instead of boxing / unpacking tables. It has a performance advantage of about 2x faster and has lower memory usage.

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  
})

      

With a little modification, we can extend the binding to start with the nth argument,

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

      

0


source







All Articles