lua-users home
lua-l archive

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



On 24-Aug-05, at 8:25 AM, Roberto Ierusalimschy wrote:

On the other hand, it would be strange to allow nil environments in
userdata but not in other types. And it would make setfenv/getfenv
more complex.

"Other types" in this sentence means "functions". No types other than functions and userdata have environments. I argue in <http://lua-users.org/wiki/UserDataRefinement> that the two concepts are radically different. So in my opinion, the complexity of setfenv/getfenv is the result of trying to make the same API do two different things.

Why does a userdata have an environment? A userdata is never the head of a call frame; it's only used as the self (or other) argument of some function, either a metamethod or a method function. And that function already has an environment. It can only get at the environment of the userdata indirectly, by pushing it onto the stack and then doing a lookup.

It's obviously useful that userdata have associated tables, I'm not arguing against that. But calling the associated table an "environment" table and then forcing it to have similar semantics to an unrelated concept (function environment tables) is just confusing (and, I would argue, inefficient for user code).

It is this line of reasoning that leads to me suggest renaming them "peer tables", and at the end of the Wiki musings, to suggest adding an additional "C peer" to the userdata structure. I think this nicely captures the semantics: a userdata is a bridge between the Lua and the C worlds; it has a peer on each side, as it were. (Or should that be pier? Mixed metaphors are so tempting.)

All of the use cases I could come up with for userdata environments have to do with storing instance-local data in them, which is why I ended up with the convention that the member function (or metamethod) environment table contains the type-common data, which is either the metatable for the userdata or some table found in the metatable, such as its __index table. This usage is consistent with member functions and metamethods for ordinary Lua tables: the metatable of the Lua table contains the type-common data, and the table itself contains the instance-local data.

Looked at that way, the "environment table" of a userdata is the equivalent not of the environment table of its associated functions, but rather the equivalent of the "table itself" of a Lua table. So logically the API function which I called lua_getpeer should return:

  for a userdata, the peer table
  for a table, the table itself
  for anything else, nothing.

This is not quite accurate, though: in the case of a Lua table being used to implement a different datatype, the Lua table is indeed the instance-local store, but you have to use rawget/rawset on it to get at the fields, since gettable/settable are presumably being overridden to provide a different view. There's no such thing as a "raw table" (that is, an object which would have the raw view of a metatable'd table), so the only option is to adopt the convention that lookups in the peer of an object must be done with the lua_raw* functions.