Search code examples
luaffiluajit

Lua/Luajit: Indexing and named method at the same time?


The Lua PIL and Luajit FFI tutorial gave two usages of __index in the metatable.

One is for indexing like obj[123], e.g.,

__index = function (self, k) return self._data+(k-self._lower)

The other usage is to define named methods, as given in the tutorial,

__index = { area = function(a) return a.x*a.x + a.y*a.y end, },

We can then make function call like obj:area().

Can I do both at the same time, e.g., direct indexing and named methods?


Solution

  • The answer, as is usual for extra-interesting code in Lua, is more metatables.

    When your __index metamethod is actually a table, Lua simply does a standard table access on the given table. This means you can set a metatable on your metatable. Then you can set an __index metamethod on this "meta-metatable".

    foo = function()
      print("foo")
    end
    
    bar = function(_, key)
      return function()
        print(string.format("bar: %s", key))
      end
    end
    
    mmt = { __index = bar }
    mti = { foo = foo }
    mt = { __index =  mti }
    t = {}
    
    setmetatable(mti, mmt)
    setmetatable(t, mt)
    
    t.foo()  -- prints: "foo"
    t.bar()  -- prints: "bar: bar"
    t.baz()  -- prints: "bar: baz"
    

    With this, when you try to access a field which is absent in both tables, lua will first try to access the top-level table which will access the first metatable which will then call your metamethod in the second metatable.


    There is also another, possibly more straight forward, answer: Use your __index metamethod to check another table for named fields:

    foo = function()
      print("foo")
    end
    
    f = { foo = foo }
    
    bar = function(_, key)
      if f[key] then return f[key] end
      return function()
        print(string.format("bar: %s", key))
      end
    end
    
    mt = { __index =  bar }
    t = {}
    
    setmetatable(t, mt)
    
    t.foo()  -- prints: "foo"
    t.bar()  -- prints: "bar: bar"
    t.baz()  -- prints: "bar: baz"
    

    Tested on Lua 5.3.