lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]



On 26-Aug-05, at 7:09 PM, Chris Marrin wrote:

It is becoming clear that, while Lua is well suited to doing just about anything (just like assembly language :-), it will never be a true object-oriented language. There will always be ':' vs. '.' issues, places where the language just can't express an object-oriented relationship (as in the case where you just HAVE to supply an explicit 'self' argument) and the fairly quirky way that some of the metamethods work.

All reasonable languages are Turing-complete :)

We could get into a long philosophical argument here: "object-oriented" does not mean "class-oriented", imho, and a prototype language like newtonscript, self or lua (to name a few) is every bit as object oriented as java or c++. (Speaking of which, I don't see much difference between the Lua example I wrote and a similar one using c++'s pointer-to-member-function syntax. Maybe that's just me.)


Don't get me wrong, I think Lua is a fantastic tool and I would never consider another language for my app. But I am really trying to do two things with Lua. First, I am using it as a basic scripting technique to get my app on the air faster. But my system is declarative and therefore I need to expose scripting to creative authors who are not experienced programmers. So I need to make the language as simple and understandable as possible.

Sure, I understand that. I used to provide objects that didn't use the : syntax at all, despite the general inefficiency. I stopped doing it because it gets to be a pain. But here's the quick outline. First you need to separate out your methods from any non-method class members; the simplest way to do that is to keep them in a separate table, and make that table the __index for the class table's __index table. This works fine if you only allow member functions to inherit; then the inheritance __index links are between method tables rather than class tables.

But instead of using a simple __index => table entry in the class table, you use some variation on this function, which I've spelled out a bit

function bind(self, methodname)
  local methodtable = self.__method
  local method = methodtable[methodname]
  if method then
    local function boundmethod(...)
      return method(self, unpack(arg))
    end
-- if they call a method once, they may call it again. So we cache it.
    self[methodname] = boundmethod
    return boundmethod
  end
end

Line 3 will pick up the inherited method from the methodtable inheritance chain. The currying procedure in lines 5-6 is ugly, slow and unreliable; it's much easier to write in C. However, that introduces a C call into the call chain, which is also ugly (it stops yield from working, for example.) There are a number of alternatives, the most interesting of which is to figure out how many parameters the method takes (which is a pretty simple modification to the debug interface) and then use a dynamically generated and memoised curry function factory. Fortunately, Lua 5.1 provides a much easier and efficient feature: just replace line 6 with

   return method(self, ...)

This mechanism is not that different from Python, and like Python it suffers from unpredictability (it really is predictable but you have to read and understand five pages of fine print to be able to predict it) and excessive malloc'ing.

Anyway, I gave up and made friends with colon, and I'm much happier for it. (One of my more common C errors is using . instead of -> and vice versa. It's not a problem unique to Lua.)

Just to harp on another issue, I was very proud when I overrode all the arithmetic opcodes to be able to handly my "primitive objects". My system has objects, for instance SFFloat, which are wrappers around primitives (float in this case). If I have:

    local a = SFFloat(1)

I really want to go:

    local b = a + 5

So I overrode __add and simply converted the object to a primitive, did the add and returned the result.

I can't help thinking that it would be easier to convert the SFFloats to numbers when you introduce them into the Lua environment, and back to SFFloats when you export them. Again, this could easilly be done with getter/setter methods.

 Cool! Well, when I overrode __lt so I could do this:

    if a < 5 then ...

it didn't work because the logic around __lt requires that both sides of the operator are the same type!

Actually, it only requires them to have the same metamethod. In Lua 5.1, primitive types have metatables, so you could define the __lt metamethod for Lua numbers to be the same metamethod as your Numeric types. This wouldn't slow down Lua's actual numeric comparisons since the Number metatable isn't consulted. Having said that, I haven't checked the code to see whether that will work -- it may be that Lua still insists that comparable objects have the same Lua type -- but the patch would be a lot cleaner.

    local c = SFBoolean(true)
    if c then ...

and that didn't work at all.

Well, it would, but SFBoolean(false) wouldn't. But why do you need to export SFBoolean(true) into the Lua environment? Surely it is easy enough to convert true <-> SFTrue and false <-> SFFalse. (In any event, in 5.1 you can give boolean a metatable.)

It would be nice if there were __toboolean and __tonumber metamethods, just like there is __tostring.

This wouldn't really work in the case of __tonumber; you would have to restrict it to the tonumber() explicit coercion.

Suppose you had   a + b

where a and b are objects. Does this mean

  meta(a):__tonumber(a) + meta(b):__tonumber(b)

or

  meta(a):__add(b)

(Consider the case of a Complex object type to put that into perspective.)

When false was introduced into the language, I observed that when you have exactly two of anything, you've probably got a design error. (If you have exactly three or exactly 11, it's even worse.) The only quantities that should appear in designs are "one" and "an arbitrary number of".

So having two values which test false fails my test. My suggestion was a __false metafield: if present, it would be expected to be the boolean false, but it could be a function, I suppose. The performance hit of calling a function for every boolean operation is a bit ugly, though.

Anyway, that didn't happen and I'm not really disappointed. (I'd still be happy to go back to only 'nil' testing false, though.) One of the things I really like about Lua is that 0 and "0" and {} are all true. (Particularly "0".) If you mean something different you should say it.

I have had to resort to a valueOf() method on these objects. So you have to go:

    if c:valueOf() then ...

not bad, but not very friendly to the authors either!

Friendly to the authors would be not trying to wrap true and false as pseudo-objects, or using the 5.1 metatables to do so. Failing that, I'd go for:

    if c == SFTrue then ...

or

    if c ~= SFFalse then

or even

    if Boolean(c) then

in preference to c:valueOf(), which doesn't give me much of a hint what it's supposed to do.

But maybe that's just me.

Anyway, good luck.