Lua - How to pass object's function as parameter to another function

anotherObject:aFunction(variable, object.doStuff) is the correct syntax.

Using a colon : with a function is just syntactic sugar for a call or declaration with an implicit self parameter as the first argument. If you would like to follow the pattern you've shown in your example in a cleaner way, you could use a helper function.

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

You then apply it like so.

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

Edit: I believe the solution to your problem will require binding at some level, without resorting to modifying the Lua interpreter or using a code translation step. This is fundamentally because functions in Lua do not carry any information about their origin. I.e., tables do not inherently own the functions that they store.

For example, the following is perfectly legitimate Lua code.

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

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

Of course, you could try a paradigm shift, but it may be too late for that if you're building an entire application based on the idea of functions being tied to the table that they have been indexed from, as you said. Therefore, I propose the following crazy solution.

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

This metatable will provide you with what you want, with inherited and bound functions to boot. You will just need to make sure that all of the tables that you want to follow this pattern also follow the method of object creation shown in the example code.

To explain, each table made in this way has any new assignment redirected into the _slots sub-table and any new retrieval checked up the _parent inheritance tree. If the type of the value is a function, then it returns a new closure with the original self that started the check bound to the function found.

Obviously, calling a function from one of these objects with the : colon syntax is going to be a silly idea, since it would evaluate to o.fn(o, o), and that is probably not what you want. Another caveat is that copying functions onto these objects, from these objects, will not work as expected. o1.newfn = o2.fn will put an o2 bound function into o1, which in turn will be re-bound to o1. The end result would be something like o2.fn(o2, o1). You will have to copy functions from the _slots table.


In conclusion: Even though this works, I would not personally recommend it in the long run, since it may be confusing to anyone used to how Lua works with tables, indexing, and functions, and there will be overhead. You might be able to do away with some it via memoizing the closures, but I'll leave that decision up to you. Good luck!

Tags:

Callback

Lua