Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

callback

lua

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

Doesn't work. Value is nil. I could find a solution doing an encapsulation in an anonymous function

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

But I find it pretty bad mkay

What is the correct syntax ?

like image 362
Rayjax Avatar asked Nov 16 '13 18:11

Rayjax


People also ask

How do you pass a function as a parameter to another function?

Function Call When calling a function with a function parameter, the value passed must be a pointer to a function. Use the function's name (without parentheses) for this: func(print); would call func , passing the print function to it.

How do you pass a function object?

To pass an object as an argument we write the object name as the argument while calling the function the same way we do it for other variables. Syntax: function_name(object_name); Example: In this Example there is a class which has an integer variable 'a' and a function 'add' which takes an object as argument.

Can you pass an object into a function?

An object can be passed to a function just like we pass structure to a function. Here in class A we have a function disp() in which we are passing the object of class A . Similarly we can pass the object of another class to a function of different class.

Can you pass in a function as a parameter?

Passing a function as parameter to another functionC++ has two ways to pass a function as a parameter. As you see, you can use either operation() or operation2() to give the same result.


1 Answers

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!

like image 169
Ryan Stein Avatar answered Oct 24 '22 02:10

Ryan Stein