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

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

无法工作。值为nil。我可以通过在匿名函数中进行封装来找到解决方案。

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

但我认为它相当糟糕,好吧

正确的语法是什么?


2
你为什么不传递对象本身呢? - hjpotter92
更少的灵活性。这意味着b必须调用test1函数,从而限制了它在a对象中的使用。 - Rayjax
4个回答

14

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

这个元表将为您提供所需的内容,并带有继承和绑定函数。您只需要确保所有想要遵循此模式的表格也遵循示例代码中显示的对象创建方法。
简单来说,以这种方式制作的每个表都将任何新分配重定向到“_slots”子表中,并检查任何新检索的“_parent”继承树。如果值的类型是“function”,则它将返回一个新闭包,该闭包将原始启动检查的“self”绑定到找到的函数。
显然,使用冒号语法从这些对象之一调用函数将是一个愚蠢的想法,因为它将计算为“o.fn(o,o)”,这可能不是您想要的。另一个注意事项是,从这些对象中复制函数,from这些对象,将无法按预期工作。 “o1.newfn = o2.fn”将把一个绑定到“o2”的函数放入“o1”中,而这将被重新绑定到“o1”。最终结果将类似于“o2.fn(o2,o1)”。您必须从“_slots”表中复制函数。 总之:尽管这有效,但我个人不建议长期使用,因为对于习惯Lua如何处理表格、索引和函数的任何人来说,这可能会令人困惑,而且会有额外的开销。您可能可以通过记忆化闭包来消除其中一些,但我将把这个决定留给您。祝你好运!

虽然你的回答很好,而且你的方法可能更加简洁,但我一直在使用基于Lua手册的面向对象模式构建整个应用程序:http://www.lua.org/pil/16.html(参见底部)。使用绑定助手函数意味着需要重写整个东西。 - Rayjax
@Rayjax 我已经更新了我的答案,提供了完整的解决方案。请告诉我您的想法。至于《Lua编程》中展示的面向对象模式,我认为其中没有任何内容涉及或鼓励您想要使用函数的方式。希望我的回答已经澄清了这一点。 - Ryan Stein
正如你所说,你的方法确实有些疯狂,但非常有趣。我认为,由于我已经完成了当前项目的大约80%,我将继续使用我已经使用的模式完成它。我知道在lua中有几种实现面向对象编程的方式,你认为哪种是最好的? - Rayjax
@Rayjax 就像很多事情一样,这取决于你在做什么、你需要什么以及你不需要什么。我通常从最简单的结构开始,并从那里开始工作。在 Lua 中可以实现各种 OO 风格,当然。因此,我认为没有特定的绝对最佳风格。但是,我必须说,我认为使用带有基于原型的继承的普通表是最简单的 OO 风格。这里是一个例子,展示了我的意思。跟随潮流,尽量不要反对语言和它所提供的东西。 - Ryan Stein

1

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

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
在 @ryan-stein 的巧妙的 bind() 解决方案的基础上,我发现在一个模块中,这个解决方案稍微更加简洁:
local m = {}
m.__index = m
m.word = 'bar'

function m:bind(fn)
    return function(...) return self[fn](self, ...) end
end

function m:foo(fn)
    print("Foo " .. self.word)
end

function m:start()
    hs.timer.new(42, self:bind('foo'))
end

-1

你的代码将会起作用。原因如Ryan所说。 我怀疑在函数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)

你的函数能够正常工作是因为a:test1没有接收到任何参数。 当传入参数时,它会打印nil。 local a = {} function a:test1(value) print(value) end local b = {} function b:test2(v1, v2) v2(100); end b:test2(_, a.test1) - Rayjax

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