问题 Lua - 如何将对象的函数作为参数传递给另一个函数


local a = {}
function a:test1(value)
    print(value)
end
local b = {}
function b:test2(v1, v2)
    v2(100);
end
b:test2(_, a.test1)

不起作用。价值是零。我可以找到一个在匿名函数中进行封装的解决方案

b:test2(variable, function(value) a:test1(value) end)

但我发现它很糟糕mkay

什么是正确的语法?


2304
2017-11-16 18:54


起源

你为什么不传递这个对象呢? - hjpotter92
不太灵活。这意味着b必须调用test1函数,从而将其用法限制在一个对象上 - Rayjax


答案:


anotherObject:aFunction(variable, object.doStuff) 是正确的语法。

使用冒号 : 带函数只是一个带隐式调用或声明的语法糖 self 参数作为第一个参数。如果您想以更干净的方式遵循您在示例中显示的模式,则可以使用辅助函数。

local function bind(t, k)
    return function(...) return t[k](t, ...) end
end

然后你就这样应用它。

anotherObject:aFunction(variable, bind(object, 'doStuff'))

编辑: 我相信你的问题的解决方案需要在某种程度上进行绑定,而不需要修改Lua解释器或使用代码转换步骤。 这基本上是因为Lua中的函数不包含任何有关它们的信息 起源,表本身并不存在 拥有 他们的功能 商店

例如,以下是完全合法的Lua代码。

function Circle:area() -- function Circle.area(self)
    -- ...
end

-- Evaluate the function in the "area" slot with Square as the self parameter.
Circle.area(Square)

当然,您可以尝试范式转换,但如果您正在构建一个完整的应用程序,可能为时已晚,因为正如您所说的那样,函数被绑定到已被索引的表中。 因此,我提出以下建议  解。

local mt = {}

function mt:__index(k)
    local v = self._slots[k]

    if v == nil then
        -- Ascend the inheritance tree.

        -- This has to be done with rawget all the way up,
        -- otherwise inherited functions would be repeatedly bound.
        local p = self

        repeat
            p = rawget(p, '_parent')
            if not p then break end
            v = p._slots[k]
        until v
    end

    if type(v) == 'function' then
        -- Return a self-bound version of the function.
        return function(...) return v(self, ...) end
    end

    return v
end

function mt:__newindex(k, v)
    self._slots[k] = v
end

--- Demo & Tests ---

local function Object(parent)
    local o = setmetatable({_slots = {}}, mt)
    if parent then rawset(o, '_parent', parent) end
    return o
end

local o1 = Object()
local o2 = Object(o1)

assert(o1.abc == nil, 'o1.abc should be nil')
o1.abc = 3
assert(o1.abc == 3, 'o1.abc should be 3')
assert(o2.abc == 3, 'o2.abc should be 3, inherited from o1')
o2.abc = 7
assert(o2.abc == 7, 'o2.abc should be 7, overriding o1')
assert(o1.abc == 3, 'o1.abc should be 3, unaffected by o2 setter')

function o1:test(bar)
    return self.abc + bar
end

assert(type(o1.test) == 'function', 'o1.test should be a function')
assert(type(o2.test) == 'function', 'o2.test should be a function, inherited from o1')

assert(o1.test(5) == 8, 'o1.test(5) should return 3 + 5 = 8')
assert(o2.test(11) == 18, 'o2.test(11) should return 7 + 11 = 18')

function o2:test2(fn)
    return self.abc + fn(7)
end

assert(o2.test2(o1.test) == 17, 'o2.test2(o1.test) should return 7 + (3 + 7) = 17')

o2.test3 = o1._slots.test -- proper function copying
assert(o2.test3(11) == 18, 'o2.test3(5) should return 7 + 11 = 18')

o2.abc = nil
assert(o2.abc == 3, 'o2.abc should be 3 again, inherited from o1 after clearing')

o2.abc = false
assert(o2.abc == false, 'o2.abc should be false, __index needs to differentiate between nil and false')

这个metatable将为您提供所需的功能,并使用继承和绑定功能进行引导。您只需确保要遵循此模式的所有表也遵循示例代码中显示的对象创建方法。

为了解释,以这种方式制作的每个表都有任何新的分配重定向到 _slots 子表和任何新的检索检查了 _parent 继承树。如果值的类型是a function,然后它返回一个原始的新闭包 self 开始检查绑定到找到的功能。

显然,用其中一个对象调用一个函数 : 冒号语法将是一个愚蠢的想法,因为它会评估 o.fn(o, o),这可能不是你想要的。另一个警告是将函数复制到这些对象上,  这些对象将无法按预期工作。 o1.newfn = o2.fn 会放一个 o2 绑定功能 o1,反过来将重新绑定 o1。最终结果将是这样的 o2.fn(o2, o1)。你必须从中复制功能 _slots 表。


结论是: 即使这样可行,但从长远来看,我不会亲自推荐它,因为任何习惯Lua如何处理表,索引和函数的人都会感到困惑,  在头顶上。你也许可以通过它取消它 memoizing 关闭,但我会把这个决定留给你。祝你好运!


10
2017-11-16 19:38



虽然你的答案很棒,而且你的方式可能更清晰,我一直在使用基于lua手册的面向对象模式构建整个应用程序: lua.org/pil/16.html (见下)。使用绑定帮助函数意味着重写整个东西 - Rayjax
@Rayjax我用完整的解决方案更新了我的答案。请让我知道你在想什么。至于所示的面向对象模式 Lua编程,我不相信那里有任何指向或鼓励以你想要使用它们的方式使用函数的东西。我希望我已经用我的答案澄清了这一点。 - Ryan Stein
正如你所说,你的方法很疯狂,虽然非常有趣。我认为,由于我已经建立了目前项目的80%左右,我将继续完成我使用过的模式。我知道有几种方法可以在lua中实现OO编程,在您看来,什么是最好的? - Rayjax
@Rayjax与很多事情一样,这取决于。这取决于你正在做什么,你需要什么,以及你不需要什么。我通常从最简单的结构开始,然后从那里开始工作。当然,Lua可以实现各种OO样式。所以我认为没有任何特别绝对的最佳风格。但我不得不说,我认为使用基于原型的继承的普通表是最简单的OO风格。这是一个 例 我的意思顺其自然,尽量不要与语言及其提供的内容作斗争。 - Ryan Stein


声明的对象方法 : 需要对象实例作为第一个参数。如果你用它调用它会自动添加 :,但是当你只传递一个函数指针时,你也需要传递它。这意味着无论何时在某个对象中传递函数,您都必须传递对象实例。这有效:

local a = {}
function a:test1(value)
    print(value)
end
local b = {}
function b:test2(obj, v2)
    v2(obj, 100);  -- object instance is always the first param of a ":"-style func
end
b:test2(a, a.test1) -- passing object instance and a function

0
2017-09-24 15:30





你的代码将是有效的。瑞恩说过的原因。 我怀疑在函数anotherObject:aFunction()中,你使用了一种错误的方法来调用object.stuff。正确的方法如下:

local a = {}
function a:test1()
    print(1)
end
local b = {}
function b:test2(v1, v2)
    v2();
end
b:test2(_, a.test1)

-1
2017-11-18 05:28



您的函数有效,因为:test1不接收任何参数。查看参数 - 打印nil local a = {}函数a:test1(value)print(value)end local b = {} function b:test2(v1,v2)v2(100);结束b:test2(_,a.test1) - Rayjax
(请编辑您的答案,以便我可以删除标记) - Rayjax