Why one callback works and the next fails on self reference

Hi All,

I’m very new to Lua and I’m trying to understand this issue. Can someone explain why this fails, or even better, show me what would be the right way to do this. The execution fails as per comment in Class A

Class Main


function setup()
    a = A()
end

function touched(touch)
    a.b:callA()
end

Class A


A = class()

function A:init()
    self.b = B(self.aDo)
    self.c = C(self.b.bDo)
end

function A:aDo()
    print("a did!")
    self.c:callB() -- fails with "attempt to index local 'self' (a nil value)"
end

Class B


B = class()

function B:init(callback)
    self.callback = callback
end

function B:callA()
    self.callback()
end

function B:bDo()
    print("b did!")
end

Class C


C = class()

function C:init(callback)
    self.callback = callback
end

function C:callB()
    self.callback()
end

The problem is that you are mixing functions and methods. (I don’t know your level of programming so I don’t know how to pitch this explanation - my apologies if I get it wrong.)

When you define a class, you define - as you do - a load of methods for use with that class. These become available when you declare an object of that class, but they have to[1] be called on a an actual object, otherwise Things Go Wrong. So when you call a method, you have to be sure that the method knows which object it should be called on.

When you pass the callbacks to the subclasses (B and C) then all you are actually passing is the method. You aren’t passing the object on which they should be called, so when the method gets called it is called with a nil object. But the callback, which is A:aDo, needs to know its object because it uses it: self.c:callB().

The basic problem is that you can’t pass a method and its object in a single pass. You can pass an object, together with all of its attendant methods but this doesn’t seem to be what you want to do. So you need to pass the method and its object together, for example you could have:

A = class()

function A:init()
    self.b = B(self,self.aDo)
    self.c = C(self,self.b.bDo)
end

Then in the B and C class, something like:

function B:init(caller,callback)
    self.caller = caller
    self.callback = callback
end

function B:callA()
    self.callback(self.caller)
end

Then the B object knows not only what method to call on its “parent”, but also who its parent actually is.

Incidentally, you can’t pass an object-and-method by passing the colon form of the method, so self.b = B(self:aDo) doesn’t work. It seems that the internal magic that converts instance:doSomething() to class.doSomething(instance) happens at the moment that lua encounters the colon and so lua tries to process the function there and then, which doesn’t work well.

[1] Well, not strictly. But if you don’t do this, you should definitely know what you are doing so it’s one of those rules that can only be broken if you know that you’re breaking it.

I liked your explanation, @Andrew_Stacy, thanks! I think I’ve had this error before somewhere.

@Andrew_Stacey: That’s brilliant and it solves my problem, thanks a lot!. I come from a Java background where there is no [straightforward] option for passing functions as parameters, so I never thought about not having the object “attached” to its function. I have no idea how passing the object as the first parameter to the function works, specially when the function takes other parameters. That’s a bit crazy but works perfectly. Thanks again.

Coming from a TeX background, I would use the analogy that when Lua encounters object:method(stuff) then it “expands” it to class.method(object,stuff). When defining the command, class:method(stuff) is replaced by class.method(self,stuff).

In Lua, most of the time everything is simply a reference in a table so can be passed around with impunity. Its type is only examined when the reference is dereferenced. So functions can be passed exactly as variables, modulo the quirk with the colon.

Incidentally, in the code you wrote I assumed that the purpose was that A does not know which method B will use for its callback. That’s why you have to pass the callback function explicitly. If A has a “standard” callback, then you can pass the entire A object to B and let B call the “standard” callback method on A. Passing the entire object is no overhead as everything is a reference so you’re only passing a reference to an entire object.

You are right Andrew, I don’t want to have a standard callback and also I want to change the behaviour on the fly if I need to, for example calling another function on the same object or even another function on a different object. Thanks for your explanation.