Calling a method by name in another object

Hi gang,

I have two objects, a Ship and a Button. The Button has an instance of Ship as a member variable, self.ship. The Button knows the name of a function, such as “accelerate”, or “turnLeft”, in self.action. I desire to call that function from Button, thereby calling back to the Ship to tell it to accelerate or whatever

In ship, I have, e.g.

function Ship:accelerate()
  self.accel = something;
end

Which does something useful. In Button, I can say, for example:

  self.ship[self.action](self.ship)

where Button self.action is the string “accelerate”.

My question: is there a way to write that line to use : syntax, or am I doomed to pass the ship instance in explicitly?

Thanks!

“You can’t prove a negative” (well, that’s not true …)

I’m pretty sure you can’t do this. The colon would have to go between the self.ship and the [self.action] which ain’t gonna happen. There are ways of wrapping up the call, but I don’t think that there are any which avoid this.

You could define an invocation function:

function Button:invoke(obj,fn)
    return obj[fn](obj)
end

Or the Ship class could have an invocation function:

function Ship:invoke(fn)
    self[fn](self)
end

But I’m sure you’ve thought of all of these and discounted them already.

I think you’re correct that self.ship has to be passed in twice. I can’t think of a way to do this with colon syntax.

The other way of connecting the ship to the button would be with a callback closure. Whether you think this is more or less elegant than your solution is probably down to taste. Callbacks are a very popular way of wiring up buttons. You might not need to pass in an instance of ship (or rather, the ship instance is stored by the closure) eg:


myButton = Button{
   title = "Go!",
   onPress = function()
      myShip:accelerate()
   end
}

@TokOut if you’re struggling to understand Ron’s question, why not try reading one of his books?
eg:

http://ronjeffries.com/articles/nature/

@yojimbo2000 , that’s interesting, I didn’t know we could do a closure like that. I’ll have to look it up and see how it works. My Button isn’t a real Button but maybe I should look one up and use that too …

Unfortunately @TokOut my book, while lovely, will not help you understand my problem here. I’ll try to explain further soon.

The button class would be something like


Button = class()

function Button:init(arg) --table of parameters
  self.onPress = arg.onPress
  self.title = arg.title 
end

and then self.onPress() to fire the callback.

It isn’t necessary to use a table for the parameters, but I find it makes for better readability then just a list of variables, especially if some of those variables are complex objects like closures.

I have found a way that works. I think in the example above, though, onPress will not be bound to the ship instance? Hm, unless arg = ship1, then … it still will need the self parameter? Guess I have to try it, I can’t visualize these very well.

I did

local f = function() ship1:accelerate() end
button = Button(ship1, x, y, f)

Probably can remove the ship parameter now as the function closure is already bound to the instance, and I think I have no more use for it. Still not sure how to pass in the NAME of the function and have it work with colon. Clearly I need to think more about this and learn something that I don’t know …

@LoopSpace for historical reasons (Smalltalk perform:), I do rather prefer the invoke style to the closure style.

@yojimbo2000 I’ve not typed in precisely your example in the few moments I’ve had to fiddle with this since asking. What I’m not seeing is
how the self.onPress() call manages to get self into the called function. I’ll try to fiddle with it when next I’m iPadding :slight_smile:

@TokOut, let me see if I can better explain what I’m trying to do.

The basic idea is that there will be buttons to tell the ship to turn, accelerate, and fire. There will be methods on Ship class, accelerate, turnLeft, turnRight, fire. So I want to create Buttons, each one bound to a specific one of those methods, and as it happens I want to create those above Ship, not inside Ship. That makes at least some difference in details.

So inside Button, when it figures out that it has been touched, it wants to have something, I called it “action”, that is bound to ship1:accelerate, or ship2: turnLeft and so on. I didn’t know how to do a function closure for some reason. So what I wanted to do originally was pass in the string for the function name on ship (“accelerate”, “turnLeft”, and so on), and inside Button, use that string to call the function.

It turns out that in Lua, writing ship.accelerate really means ship[“accelerate”], no more and no less. So you could say, if self.action contains “accelerate”, self.shipself.action and that would fetch the function named “accelerate” and call it. BUT … since Ship is an object, I really want to call self.ship:accelerate(), which passes the ship in as the self argument. But I couldn’t find a syntax that allowed that so wound up using self.shipself.action, and my question was if there was a simpler way.

Now @yojimbo2000 suggests passing a “function closure”. Basically a closure is a partly-applied function call, which can be completed later. In the case I did, I defined function f:

local f = function() ship1:accelerate() end

and then passed f to the Button:

button = Button(x, y, f)

Now in this case, f has not been called yet (since I didn’t say f() in the button creation, so inside the Button if I save that parameter in, say action:

Button:init(x,y,action)
  self.action = action
  ...
end

then when the button wants to do the action, it just says:

  self.action()

Adding the parens calls the function that is stored in self.action, namely my function f. f, in turn, calls ship1: accelerate, which is what we want.

This is all rather arcane, and kind of advanced, so if you’re new to Lua and/or to programming with closures, it’s tricky to understand. I found it tricky myself, though I understand it when done this way. I think @yojimbo2000 has another way in mind and I’m not sure I understand that yet.

Does that help? I hope so.

@LoopSpace hm, no, I hadn’t thought of those examples toward the end of your note, may have to try them to get my arms around them. Definitely there appears to be no rational syntax to do what I was after without passing in the “self” variable.

Thanks, must think about what you suggested there …

The closure method (which is what I tend to use in my UI stuff) doesn’t get round the problem of wanting to call the function by name rather than by reference.

I think that if I wanted to make the code in the button as simple and clear as possible, I would go down the invocation route. You could even have the invocation code include a test for whether or not the desired action exists.

Of course, if all else fails there’s always the evil eval … (or load as lua has it)

I think in the example above, though, onPress will not be bound to the ship instance?

onPress is bound to the ship instance, because the closure remembers its up-values, in this case the instance.

Defining the callback as a separate, named function like you have above does make for a less verbose function call.


myShip = Ship()

local function onPress()
      myShip:accelerate() --upvalue myShip is remembered by the closure
end

local x, y = 100, 50

myButton = Button(x, y, onPress)

What I’m not seeing is how the self.onPress() call manages to get self into the called function

That’s the magic of closures!

local function onPress()
      myShip:accelerate() --upvalue myShip is remembered by the closure
end

myShip is self, the : passes this into the accelerate() method. The closure stores its upvalue, myShip, binding it to the callback.

Yes, I see that one. It’s above that where you say “the button class would be something like …” That I didn’t see.

Anyway, now I know N ways to do it and I think I like the invoke best. :slight_smile: Thanks!