As expected, the __method metamethod was a measurable improvement over
using __index and creating closures. But to my surprise, I had to
cheat quite a bit to get within 20% of the performance of using colon
syntax.
I think you're actually selling the colon syntax short in these tests. You've defined new_colon(t) using:
return setmetatable(o, {__index = function(t, k) return funcs[k] end, })
but, most people who use the colon syntax are probably skipping that extra level indirection by using the __index=table special case. i.e:
return setmetatable(o, {__index = funcs, })
In my own tests, switching to that syntax speeds up the colon-syntax numbers by about 25%.
I also think there's an important option that you're overlooking here. I use implicit-closure creation to implement object methods in some of my own code, but, I take some care to avoid repeated creation of new closures. It's still significantly slower than the colon syntax, but, much of the time, it's far better than just dropping down new closures anytime index is called (on my machine, the approach is about on par with the 'fast' method, assuming the garbage collector is turned off.)
The __method idea you're playing with feels like something that could be a useful hack -- but, I'm worried about some of the strange edge cases it would add to the language. For example, it's unclear how to handle userdata that have definitions for both __index and __method. If all you want is a version of the closure-style object syntax with relatively low overhead, I'd suggest looking at some version of lazy closure creation. It's not as fast as the colon-syntax, but, it's also not nearly as slow as you've been assuming.
-Sven
Here's a lazy implementation that's compatible with your tests:
local function new_lazy(t)
-- only store cached member functions until the
-- gc gets around to freeing them.
local member_functions=setmetatable({},{__mode="kv"})
local mt = {}
function mt.__index(t,k)
-- return t[k], if it exists
local var = rawget(t,k)
if var then
return var
end
-- check the member function cache
var = rawget(member_functions,k)
if var then
return var
end
-- create a new cached member function
var = rawget(funcs,k)
if var and type(var)=='function' then
local function self_fun(...)
return var(t,...)
end
rawset(member_functions,k,self_fun)
return self_fun
end
-- otherwise, behave as if __index=funcs
return funcs[k]
end
return setmetatable( t or {a = 0} ,mt)
end