[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Re: 'table' as fallback for tables
- From: Philipp Janda <siffiejoe@...>
- Date: Sat, 25 Jun 2016 01:10:04 +0200
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