Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Can you use a method as a variable?

edited March 2013 in General Posts: 557

It's easy to use a function as a variable. This works fine:

function printTest (printMe)
    print(printMe)
end

function doThisToThis(functionA,target)
    functionA(target)
end

trial = "it works"

doThisToThis(printTest,trial)

Result: "it works" gets printed.

But when I add this:

function MyClass:methodPrint(printMe)
    print(printMe)
end

doThisToThis(MyClass:methodPrint,trial)

I get an error saying "function arguments expected near ','", next to the last line.

So then I try it this way:

doThisToThis(MyClass:methodPrint(),trial)

Which gives this error: "attempt to call local 'functionA' (a nil value)", next to "functionA(target)" inside the "doThisToThis" function definition.

Is what I'm trying to do not possible? Or is there some hope in this cruel world after all? :)

Tagged:

Comments

  • Posts: 503

    You have to write MyClass.methodPrint instead, otherwise it tries to parse it as you are trying to make a function call, it seems like. But you also have to call the method in the right context, so in your example the argument trial should be an instance of MyClass.

  • Posts: 557

    but but but--making trial an instance of MyClass ruins the whole point of having a function that can apply any given function to any given target. The parameters "functionA" and "target" in "doThisToThis" should be able to know nothing at all about each other, otherwise it's just a weird way of having an object use one of its own functions on itself, no?

    Thanks for helping, it's great that people here are so responsive.

  • Posts: 503

    Another solution would be to bake the call into a function, if you want to call print on instance x for example.

    doThisToThis(function(t) x:methodPrint(t) end, trial)

  • Posts: 557

    tnlogy: that's a very clever way to do it.

    Sure seems like you should be able to do it a less-circuitous way, though. But good work-around, thanks!

  • Posts: 2,161

    When passing a method, you also have to pass the object on which to call that method. I don't know of any way around this.

    function doThisToThisOnThat(functionA,target,object)
        functionA(object,target)
    end
    
    function MyClass:methodPrint(printMe)
        print(printMe)
    end
    
    MyInstance = MyClass()
    
    doThisToThisOnThat(MyClass.methodPrint,trial,MyInstance)
    

    Or

    function doThisToThisOnThat(functionA,target,object)
        object[functionA](object,target)
    end
    
    function MyClass:methodPrint(printMe)
        print(printMe)
    end
    
    MyInstance = MyClass()
    
    doThisToThisOnThat("methodPrint",trial,MyInstance)
    
  • edited March 2013 Posts: 580

    Heh, I actually just wrote this today for something else that I'm going to be posting about sometime tonight, but I think it might be relevant to the discussion. A way that I've dealt with this is to create a function that returns a bound member function call. For example:


    function bind(obj, methodname) return function(...) return obj[methodname](obj, ...) end end

    Now, whenever you need something to bind an object with it's method for use in a callback, you can use:


    -- add callback to example touch event manager touchManager:addCallback(bind(myObj, 'touched')) -- later on, touchManager calls our bound callback with a touch object as an argument: callbacks[i](touch) -- which will functionally equate to: -- myObj:touched(touch)

    I've even actually taken it a step further: because passing the same inputs to bind() will always generate functionally identical outputs, I memoize the bound method. The main reason that I did this was because I wanted a simple way to be able to compare bound methods for later removal from callback lists. For example:


    local bound1 = bind(someObj, 'someFunc') local bound2 = bind(someObj, 'someFunc') print(bound1 == bound2)

    Will print 'false', because even though the functions generated by bind() in this case are functionally identical, they are not equal because they are completely separate objects. With memoized bound methods, we only need to create the bound method the first time it's needed; subsequent times, we'll just return the one we created earlier. Now:

    -- caller() is bind() but with memoization
    local bound1 = caller(someObj, 'someFunc')
    local bound2 = caller(someObj, 'someFunc')
    
    print(bound1 == bound2)
    

    Will print 'true'. This is especially useful for event systems, where (especially in Lua) you might find yourself needing to keep a separate reference to your bound callback so that you can de-register it later. Using caller(), you don't have to, since it memoizes the bound method. Now you can register and deregister the same way, with no additional reference tracking:


    touchManager:addCallback(caller(myObj, 'touched')) touchManager:removeCallback(caller(myObj, 'touched'))

    I've also added another whistle to the caller() function, which is that the tables used for memoization keep weak references, so that once there are no more references to the object and bound method, they will be garbage collected automatically. Here is the full code for caller():

    -- caller
    
    local _weakKeyMT = {__mode = "k"}
    local _weakValueMT = {__mode = "v"}
    local _methods = setmetatable({}, _weakKeyMT)
    
    function caller(obj, method)
        local methods, call = _methods[obj]
    
        if methods then
            call = methods[method]
        else
            methods = setmetatable({}, _weakValueMT)
            _methods[obj] = methods
        end
    
        if not call then
            call = function(...) return obj[method](obj, ...) end
            methods[method] = call
        end
    
        return call
    end
    
    
Sign In or Register to comment.