Table clone and table append functions

With luau’s strict typing everything must be statically resolvable by the interpreter. This becomes an issue when one wishes to put the members of one table into another.

For example, I have a class with a prototype. This is what I have to do right now to get all the members to be available to the prototype, the constructor, and the type.

type Object = {
   foo: ()->(),
   bar: ()->()
}

local proto = {
   foo = function() end,
   bar = function() end
}

function make() : Object
   return {
      foo = proto.foo,
      bar = proto.bar
   }
end

This is time-consuming and annoying, because any new member now has to be added to three places (type declaration, proto implementation, and make assignment).

What I want to do, and would do before strict luau, is dynamically assign the members like this:

function make()
   local new = {}
   table.foreach(proto, function(k, v)
      new[k] = v
   end)
   return new
end

But this doesn’t get captured by luau, because the members of proto become anonymized as k and v.

So my suggestion is clone and append functions. These functions can be interpreted directly by luau to prevent the members of proto from being anonymized.

function make() : Object
   return table.clone(proto)
end
-- ...
function make() : AnotherObject
   return table.append(proto, {
      per_instance_property = {},
      another_per_inst_prop = 1234
   })
end

Once this is available, I can reduce the number of foo and bar definitions from three each to only one each:

local proto = {
   foo: ()->(),
   bar: ()->()
}

function make : Object
   return table.clone(proto)
end

type Object = typeof(make())

My ideal API for these functions is

  • table.clone(source) -> new
  • table.append(source, target) -> target
10 Likes

What about declaring the return type of make?

local function make():typeof(proto)
	local new = {}
	for k,v in pairs(proto) do -- (foreach is deprecated)
		new[k] = v
	end
	return new::typeof(proto)
end

The problem with table.clone and table.append is their semantics. In many cases, a “deep copy” where the tables reference by the table are also cloned is desired, and in many cases it isn’t desired. If proto references a table, should that table also be cloned? If tables referenced by the table are also cloned, what happens if the table referenced by proto is itself (how are cyclic references resolved), or another object referenced by proto which would be cloned (if a table is referenced twice, does it get cloned twice or once)?

table.append would be confusing, as currently all table library functions work using arrays. The name seems like something that would make sense if it worked for arrays (copying values of source into target at the end of the array), so it could be easily confused.

2 Likes

Luau can be confusing in its syntax. type Object = typeof(make()) will statically interpret the return value, so those anonymized members will still not be included. Instead, luau will interpret the type as {[typeof(k)]:typeof(v)} which gets essentially resolved to {[any]:any}.

Deep copy sounds like the most intuitive behavior. Cyclic and duplicated tables can be cloned then referenced.

t = {}
x = {a = t, b = t}
y = table.clone(x)
print(y.a == y.b) --> true
---
x = {}
x.a = x
y = table.clone(x)
print(y.a == y) --> true

Foreach doesn’t. “Append” is just a suggestion.

1 Like

typeof(make()) works fine for me?

--!strict
local proto = {
	foo = function()end,
	bar = function()end
}
local function make():typeof(proto)
	local new = {}
	for k,v in pairs(proto) do
		new[k] = v
	end
	return new::typeof(proto)
end
type Object = typeof(make())
local o:Object = {} -- W000: (14,1) Table type 'o' not compatible with type 'proto', missing fields 'bar', and 'foo'
local o:{[any]:any} = {} -- no warning

foreach is deprecated.
Lua 5.1 Reference Manual

  • Functions table.foreach and table.foreachi are deprecated. You can use a for loop with pairs or ipairs instead.

For singleton objects it may not make sense (contains a reference to a specific value, e.g. a table of functions). Would other things like RaycastsParams be duplicated too (or maybe even functions), or would it only clone tables? How a table should be cloned is different for different use cases, it doesn’t make sense to have a function for a specific way of cloning a table.

2 Likes

Only tables would be copied. The intent is not to duplicate any arbitrary data, just tables.

1 Like