0

I have a personal project and a pure lua writed object module which provides metatable with methods filter map etc to a table , I don't want to require and setmetatable for every line local foo={}

Egor Skriptunoff
  • 23,359
  • 2
  • 34
  • 64
tcpiper
  • 2,456
  • 2
  • 28
  • 41

2 Answers2

1

Global Constructor

I have a personal project and a pure lua writed object module which provides metatable with methods filter, map etc to a table , I don't want to require and setmetatable for every line local foo={}.

You can avoid the need for require by just ensuring that your "object module" is always loaded first, setting global variables. I consider require to be cleaner however as it makes clear the dependencies of your files (and does not pollute the global environment).

To avoid the need for setmetatable, you can write yourself a constructor function:

local metatable = ...
function Table(t) -- NOTE: global function
    return setmetatable(t, metatable)
end

then, in another file which you ensure only runs after this file was executed:

local foo = Table{}

You might want to shorten Table to T if you use this very often.

Setting a metatable for all (new) tables

Do you really want this?

First of all: You probably do not want local t = {} to set a metatable on t. This would mess with linters like Luacheck while also making your code hard to follow for everyone familiar with Lua but unfamiliar with this hack.

Setting a metatable with __index also interferes with the usage of tables as dictionaries / hash maps; users now need to use rawget / rawset to circumvent your metatable. Consider the following snippet:

local dict = { filter = "bar" }
print(dict.filter) -- "bar", as expected
print(dict.map) -- filter function - unexpected
print(rawget(dict, "map")) -- nil, as expected

It will also harm performance of every table access. Do you really want this just for some syntactic sugar?

Furthermore, if you heavily set metamethods (such as the arithmetic metamethods) even if it doesn't really make sense, you again get unexpected behavior by allowing passing tables where numbers are expected etc. Lua's partial strictness when dealing with incompatible types is what distinguishes it from JS.

How to do it

how to automatically set default metatable to every newly created table?

This is not possible in general; the proper way to set metatables is to explicitly use constructors.

debug.setmetatable, primitives & functions

Using debug.setmetatable, you can set "shared"/"common" metatables for all primitive types (boolean, number, string) as well as functions. You can not set a shared metatable for objects this way however.

Hooking global/environmental variable access

This is what koyaanisqatsi's snippet does: It catches global variable access and sets the metatable on all new table global variables. This is insufficient as it forces you to use global/environmental variables, which is both bad for performance and code quality. It will in particular not work at all for local variables such as the local foo = {} in your example. It will also not work for temporary variables in expressions (consider ({...}):filter(...)).

Debug Hooks

The following approach would be more reliable than hooking environmental variable access:

  • Set a debug hook that runs after every instruction.
  • Iterate over locals & upvalues and set the metatable for each table; perhaps remember old/new locals/upvalues/tables.
  • Eventually consider doing this recursively, for structures such as {{}}.

Obviously this would be awfully slow. It is very likely that there still exist many "edge cases" this doesn't catch (what about table creation in C, for instance?).

Forking Lua

The only proper solution would be to add such a feature - a default metatable for all tables - to the language by forking it and implementing this right in the function where Lua creates new tables. This is the only way this could be implemented adequately - that is, with adequate performance & reliability.

Luatic
  • 8,513
  • 2
  • 13
  • 34
0

For this you can set/use the Metamethod __newindex that triggers at defining something New.
For "every newly table" the right Place is: _G

_G = setmetatable(_G,
  {__index = table,
   __newindex = function(tab, key, value)
    print('[__newindex]:', tab, key, value)
    if type(value) == 'table' then
      rawset(tab, key, setmetatable(value, getmetatable(tab)))
    else
      rawset(tab, key, value)
    end
    return
   end
})

--[[ Example of use:
> a = {}
table: 0xf7ed2380   a   table: 0xf7ed9738
> a:insert('Hi There!')
> print(a:concat())
Hi There!
> a[#a + 1] = {}
[__newindex]:   table: 0xf7ed9738   2   table: 0xf7edbe40
]]

What will not work under LuaJIT (Lua 5.1)?

  1. __gc will not be triggered at all
  2. table.insert({'I (Key [1]) am a string in a table'}) will not triggered by __newindex
koyaanisqatsi
  • 2,585
  • 2
  • 8
  • 15