lua-users home
lua-l archive

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


On Tue, Mar 13, 2001 at 03:20:30PM -0300, Luiz Henrique de Figueiredo wrote:
> Except for the (temporary) lack of official tolua for 4.0, the 4.0
> API should be easier to program than the 3.2 API. Of course, it
> takes getting used to it, since it was a major change.

With all due respect, I have to disagree. I've just completed porting
both tklua (I will make an alpha release soon) and our own project to
the new API, and I found that using it requires substantially more
attention to detail than the old one.

Please take the following as constructive criticism, and please also
note my suggestion at the end of this message.

To the credit of the new API, it is more consistent than the old
one. But IMHO, that does not necessarily make it easier to use. There
are two stumbling blocks with it that have annoyed me over and over
again during the porting process.

The first one is that lua 4 programs are often longer than their
equivalent lua 3 programs. The second one is that keeping track of
negative stack indices is simply too hard to do. 

As a demonstration, suppose that you do some computations on a table
first and then also have to pass this table to a function as an
argument. In the old API you could basically write:

lua_beginblock();
lo = lua_getglobal("table");
lua_pushobject(lo);
lua_pushstring("index");
value = lua_getnumber(lua_gettable());
lua_pushobject(lo);
if (value == ...) {
   lua_pushnumber(123);
   lua_callfunction("processA");
}
else {
   lua_pushnumber(456);
   lua_callfunction("processB");
}
lua_endblock();

In the new API the shortest version I can come up with is:

lua_getglobal(L, "table");
lua_pushstring(L, "index");
lua_gettable(L, -2);
value = lua_tonumber(L, -1);
if (value == ... ) {
   lua_getglobal("processA");
   lua_pushvalue(L, -3);
   lua_pushnumber(L, 123);
}
else {
   lua_getglobal("processB");
   lua_pushvalue(L, -3);
   lua_pushnumber(L, 456);
}
lua_call(L, 2, 0);
lua_pop(L, 2);


Related patterns are very common in tklua. Two questions: 

1. Which version is shorter?
2. Perhaps more important, which version is easier to write, read, and
   understand?

Against the readability problems, perhaps we can do the following:

tos = lua_gettop(L);
lua_getglobal(L, "table");
lo = lua_gettop(L);
lua_pushstring(L, "index");
lua_gettable(L, lo);
value = lua_tonumber(L, -1);
if (value == ... ) {
   lua_getglobal("processA");
   lua_pushvalue(L, lo);
   lua_pushnumber(L, 123);
}
else {
   lua_getglobal("processB");
   lua_pushvalue(L, lo);
   lua_pushnumber(L, 456);
}
lua_call(L, 2, 0);
lua_settop(L, tos);

This is at the cost of making the new program even longer. This
quickly becomes very tedious to write, especially when a function has
to manipulate and query lots of objects. 

Overall, the new API feels more low-level than the old one; perhaps
the difference between an assembler that supports symbols and one that
does not. If we assume that the number of statements is an approximate
metric for the bug count, it seems also more likely that a bug will
slip through.

I spent some time thinking on what specifically made the new API
harder to use than the old one, and at least for me it is the fact
that the negative indices require you to keep a mental model of the
complete stack at every single line of code. In the old API this was
more localized - you had to care only about pushing the objects on the
stack that were needed for the next operation.

Using positive indices is far easier. Unfortunately, the API does not
make it easy to use them. In order to obtain them, it is first
necessary to call lua_gettop() after the last operation. As I already
mentioned, this makes the code longer and more tedious to write.

I propose a small enhancement to the API that IMHO would result in a
huge improvement in usability: Let every function that pushes a result
on the stack return the stack index of the result.

For example, lua_getglobal() would work like this:

int enhanced_getglobal(lua_State *L, const char *var)
{
  lua_getglobal(L, var);
  return lua_gettop(L);
}


lua_gettable() would work analogously. lua_call() would look like this:

int enhanced_call(lua_State *L, int nargs, int nret)
{
  lua_call(L, nargs, nret);
  return lua_gettop(L) - nret;  /* index of the first returned object */
}


I would very much like to see this in the next version of the lua API,
or as an official wrapper library in the next version of lua. Then it
would be possible to do this:

tos = lua_gettop(L);
lo = lua_getglobal("table");
lua_pushstring(L, "index");
value = lua_tonumber(L, lua_gettable(L, lo));
if (value == ...) {
   lua_getglobal("processA");
   lua_pushvalue(L, lo);
   lua_pushnumber(L, 123);
}
else {
   lua_getglobal("processB");
   lua_pushvalue(L, lo);
   lua_pushnumber(456);
}
lua_call(L, 2, 0);
lua_settop(L, tos);

This is no longer than the version that uses negative indices, and no
more tedious to write than the lua 3 version, but still as easy to
write, read, and understand (IMHO) as the lua 3 version.

- Christian