[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Re: More on environments and objects
- From: Rici Lake <lua@...>
- Date: Sat, 27 Aug 2005 02:27:46 -0500
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.