I've read about metatables (here and here) and I asked myself if I could create a function call proxy by just adding a function with the same name to the __index
table, so that it calls the index function (mine), and then I can call the normal one.
This is what I've tried:
local test = {}
test.static = function () print("static") end -- The normal function
local old = getmetatable(test) or {} -- Get the orginal meta
old.__index = {
static = function () print("hook") end -- Adding my function
}
test.static() -- call just for test ( prints -> static )
test = setmetatable( test , old ) -- setting the metatable
test.static() -- call just for test ( prints -> static, but should be hook )
Try giving the official source a read, specifically §2.4 – Metatables and Metamethods
, and the description of the __index
methamethod, which reads:
__index: The indexing access
table[key]
. This event happens when table is not a table or when key is not present in table. The metamethod is looked up in table.Despite the name, the metamethod for this event can be either a function or a table. If it is a function, it is called with table and key as arguments, and the result of the call (adjusted to one value) is the result of the operation. If it is a table, the final result is the result of indexing this table with key. (This indexing is regular, not raw, and therefore can trigger another metamethod.)
We can see that your thinking is backwards. Properties on a table referenced by the __index
metamethod are only looked up if the original table does not contain the key.
If you want to 'hook' a function you'll need to overwrite it, possibly saving the original to restore it later. If you want to tack functionally on to an existing function, you can write a neat little hooking function, which simply creates a closure around the functions, calling them in turn.
local function hook (original_fn, new_fn)
return function (...)
original_fn(...)
new_fn(...)
end
end
local test = {}
test.foo = function () print('hello') end
test.foo = hook(test.foo, function () print('world!') end)
test.foo() --> prints 'hello' then 'world!'
Alternatively, you can switch between metatables, assuming the original table never overrides the keys of interest, to get different results:
local my_table, default, extra = {}, {}, {}
function default.foo () print('hello') end
function extra.foo() print('world!') end
local function set_mt (t, mt)
mt.__index = mt
return setmetatable(t, mt)
end
set_mt(my_table, default)
my_table.foo() --> prints 'hello'
set_mt(my_table, extra)
my_table.foo() --> prints 'world!'