如何为 Lua 实现 bind() 函数?

我想为 Lua 实现 bind(),在 Javascript 中广泛用于创建闭包。

以下代码演示了一个参数的情况:

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

local x = { data = 1 }
function x.print(self)
  print self.data
end

outputX = bind(x.print, x)
outputX() -- print 1

我的问题是:如何支持任意数量的绑定参数?

点赞
用户2328287
用户2328287

使用 lua-vararg 可以编写:

local va = require "vararg"

function bind(f, ...)
  local outer_args = va(...)
  local function closure (...)
    return f(va.concat(outer_args, va(...)));
  end
  return closure;
end

bind(print, 1, 2, 3)(4,5,6)

这是纯 Lua 实现的:

function packn(...)
  return {n = select('#', ...), ...}
end

function unpackn(t)
  return (table.unpack or unpack)(t, 1, t.n)
end

function mergen(...)
  local res = {n=0}
  for i = 1, select('#', ...) do
    local t = select(i, ...)
    for j = 1, t.n do
      res.n = res.n + 1
      res[res.n] = t[j]
    end
  end
  return res
end

function bind(func, ...)
  local args = packn(...)
  return function (...)
    return func(unpackn(mergen(args, packn(...))))
  end
end

bind(print, 1, nil, 2, nil)(3, nil, 4, nil)
2013-08-14 10:49:20
用户734069
用户734069

因为 Lua 处理 ... 和多个返回值的方式,显而易见的方法无法正常工作:

function bind(func, ...)
  local args = {...}
  return function (...)
    return func(unpack(args), ...)
  end
end

这样做是行不通的,因为unpack函数返回的多个值将会被 调整为一个返回值,这是表达式中使用方式导致的

你可以这样做,它将在一定程度上起作用:

function bind(func, ...)
  local nargs = select("#", ...)
  local args = {...}
  return function (...)
    local newArgs = {...}
    local fullArgs = {}
    copy(fullArgs, args)
    copy(fullArgs, {...})
    return func(unpack(fullArgs))
  end
end

上面的 copy 函数只是一个简单的重复利用元素从一个表复制到另一个表的公用函数。

在这里的限制是不能有任何参数在 bind 调用或函数对象中是 nil。如果有的话,那么这些参数及其后面的参数都将被删除。

使用 C API 可以很容易地做到准确,但由于 Lua 语言的限制,这种方法需要正确地处理 nil 相当困难。

2013-08-14 10:49:49
用户2458544
用户2458544

定义一个 unpackN 函数来解开一个参数表:

function unpackN(argss, i)
    i = i or 1

    local iLocal = i
    for _, args in ipairs(argss) do
        local argsN = #args

        if iLocal <= argsN then
            return args[iLocal], unpackN(argss, i+1)
        end

        iLocal = iLocal-argsN
    end
end

然后像下面这样使用它:

function bind(func, ...)
    local A = {...}
    return function (...)
        local B = {...}
        return func(unpackN {A, B} )
    end
end
2013-08-14 13:25:24
用户783743
用户783743

尝试一下:

function bind(func, ...)
    local rest = {...}

    return function (...)
        local args = {}

        for i = 1, #rest do
            args[i] = rest[i]
        end

        for i = 1, select("#", ...) do
            table.insert(args, select(i, ...))
        end

        return func(unpack(args))
    end
end

现在你拥有了一个可变参数的绑定函数:

function add(...)
    local sum = 0

    for i = 1, select("#", ...) do
        sum = sum + select(i, ...)
    end

    return sum
end

local add_2_3 = bind(add, 2, 3)

print(add_2_3(5))

希望这有所帮助。

2013-08-14 14:10:40