lua-users home
lua-l archive

Re: 'table' as fallback for tables

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


Am 24.06.2016 um 20:32 schröbte Dirk Laurie:
[1]
Tables have individual metatables. Tables freshly constructed
by a table literal have none. The following is an error:
({24,6,2016):concat"/" --> (not yet) 24/6/2016
Would it not be nice if tables that have no metatable fall back
to {__index=table}? [2] Only table.pack does not have a table as
first argument, and we have a precedent for that in 'string',
which is also the __index of a metatable but contains functions
cannot be used with object notation.
Strange thing, I've been thinking about this as well, but I'm coming from a different direction: As I've mentioned elsewhere I'm not a big fan of the `table.pack()` function, but one nice thing about it is that we now have an official way to represent sparse tables (with that extra `n` field that `table.pack()` sets). Strange thing is that `table.unpack()` *does not* honor that `n` field although those two functions appear to be inverses of each other. What `table.unpack()` *does* honor is the `__len` metamethod, so I thought maybe `table.pack()` should set a metatable on its result. Actually, most (all?) of the table functions in Lua 5.3 respect metamethods now, so I wondered what it would take to make them work with sparse tables as well, and it's not that much: I already mentioned `__len`. For `t[#t+1] = x` to work you'll also need a `__newindex` metamethod that updates `n` if the key is an integer greater than `n`. Now `table.insert()` (mostly) works as well. `table.remove()` needs some wrapping because removing an existing element does not trigger `__newindex` (and assigning `nil` to `t[t.n]` probably should not shrink `n` if `t` is allowed to have `nil` values in it, anyway). `table.pack()` is wrapped to set the metatable on it's result, and `table.sort()` gets a new default comparator that moves `nil` values to the end of the array. And that's mostly it! I've attached a proof of concept. I've also added an `__index` metamethod for OO syntax and `__call` for table iteration via `for i,v in array do ...` (no need for `__next`), although I don't care much about those two features.
Back to the original proposal:
Lua already knows how many elements -- including `nil`s -- should be added to a table in a lot of cases, e.g.
 { ... } -- should be `select('#', ...)`
 { a = 1, ... } -- still `select('#', ...)`
 { 1, f() } -- should be 1 + number of return values
 { 1, 2, 3 } -- should be 3
 { 1, nil, nil } -- ditto
 { 1, 2, [4]=4 } -- 4
 { 1, 2, [x]=4 } -- 2, or x if x is an integer > 2
 { 1, 2, [x]=nil } -- same
 and could set `n` and the metatable for us automatically.
The only case (I can think of) that's really missing is the empty table:
 local t = {} -- array with n=0 and metatable, or just plain table?
So far I've thought of two possible solutions:
1. new syntax sugar for creating arrays (`local t = []`?)
2. a special case in the parser for a literal `{ n = 0 }`
3. a constructor function for creating arrays (hopefully somthing shorter than `local t = table.pack()`!) TL;DR: I wouldn't set a default metatable on _all_ tables, only those that the Lua parser deems array-like. And maybe that can also solve (or at least mitigate) the array-with-holes debacle ...
And now, discuss! :-)
Philipp
[1] Since feature request season seems to be open.
[2] Sean will no doubt be able to cite when this exact proposal
was first made, unless it was before 2009.
local M = {}
local Meta = {
 __len = function( t )
 return t.n
 end,
 __newindex = function( t, k, v )
 if type( k ) == "number" and k % 1 == 0 then
 if k > t.n then t.n = k end
 end
 rawset( t, k, v )
 end,
 __index = M,
 __call = function( self, _, var )
 var = (var or 0)+1
 if var <= self.n then return var, self[ var ] end
 end,
}
-- make a plain table an array
local function setmeta( t )
 local mt = getmetatable( t )
 if mt == nil then
 t.n = #t
 setmetatable( t, Meta )
 elseif mt ~= Meta then
 error( "conflicting metatable", 3 )
 end
end
-- copy all functions from the table library
for k,v in pairs( table ) do
 M[ k ] = v
end
-- replace some of the original table functions ...
-- table.pack()
function M.pack( ... )
 return setmetatable( table.pack( ... ), Meta )
end
-- table.remove()
function M.remove( t, ... )
 setmeta( t )
 local n, v = t.n, table.remove( t, ... )
 if (... or n) > n then
 t.n = n
 elseif n > 0 then
 t.n = n - 1
 end
 return v
end
-- table.insert()
function M.insert( t, ... )
 setmeta( t ) -- in case you want to insert `nil`s
 return table.insert( t, ... )
end
-- table.move()
function M.move( a1, f, e, t, ... )
 if select( '#', ... ) == 0 then
 setmeta( a1 )
 else
 setmeta( ... )
 end
 return table.move( a1, f, e, t, ... )
end
-- table.sort()
local function cmp( a, b )
 if a == nil then
 return false
 elseif b == nil then
 return true
 else
 return a < b
 end
end
function M.sort( t, ... )
 if select( '#', ... ) > 0 then
 return table.sort( t, ... )
 else
 return table.sort( t, cmp )
 end
end
-- return module
return M

AltStyle によって変換されたページ (->オリジナル) /