2

in Lua we do OO programming like this:

MyClass = {}

function MyClass:new()
    local obj = {}
    setmetatable(obj, self)
    self.__index = self

    obj.hello = "hello world"

    return obj
end

function MyClass:sayHi()
    print(self.hello)
end

function main()
  local obj = MyClass:new()
  obj:sayHi()
end

When working with more compelx stuff, usually I take advantage of Lua's metamethods to proxy function calls and do whatever I need with it, like arguments parsing etc, by adding this:

MyClassMeta = {}

function MyClassMeta.__index(obj, funcName)
    return function (self, ...)
        //do some stuff
        print("you called " .. funcName .. " with args", ...)
    end
end

and changing the line:

setmetatable(obj, self)

to:

setmetatable(obj, MyClassMeta)

every function I call with an instance of MyClass will execute the code implemented in MyClassMeta.__index metamethod.

What I want to do now is inherit the MyClass existing methods, and execute MyClassMeta.__index only for functions that are not part of MyClass.

In the above example, the code will always execute the MyClassMeta.__index metamethod even when calling MyClass:sayHi():

function main()
  local obj = MyClass:new()
  obj:sayHi("hello")
end

you called sayHi with args hello

Webert Lima
  • 13,197
  • 1
  • 11
  • 24

1 Answers1

2

When you set __index to a table, it will look for properties on that table and return them if they don't exist on the instance. Since sayHi exists on the MyClass table it is used.

self.__index = self

When you set __index to a function, it can return anything for properties that don't exist on the instance. You can check if the key exists on the MyClass table and return it, then do something else if it doesn't:

MyClass = {}

MyMetatable = {
  __index = function(obj, key)
    if MyClass[key] ~= nil then return MyClass[key] end
    return function(self, ...)
      print("you called "..tostring(key))
      print("  self.hello is '"..tostring(self.hello).."'")
      print("  with args", ...)
    end
  end
}

function MyClass:new()
    local obj = {}
    setmetatable(obj, MyMetatable)

    obj.hello = "hello world"
    return obj
end

function MyClass:sayHi()
    print(self.hello)
end

function main()
  local obj = MyClass:new()
  obj:sayHi()
end

local obj = MyClass:new()
obj:sayHi("hello")
obj:somethingElse(1, 2, 3)

Version with Egor's comment

MyClass = {}

setmetatable(MyClass, {
  -- if it's not found on MyClass, return a function
  __index = function(self, funcName)
    return function(self, ...)
      print("you called "..funcName.." with args", ...)
    end
  end
})

function MyClass:new()
  local obj = {}
  -- if it's not found on obj, try self (MyClass)
  setmetatable(obj, { __index = self })
  obj.hello = "hello world"
  return obj
end

function MyClass:sayHi()
    print(self.hello)
end

local obj = MyClass:new()
obj:sayHi()
obj:somethingElse(1, 2, 3)

When creating an object this sets the __index of the new object's metatable to MyClass, and MyClass's metatable's index to the function that is a fallback. So if the property isn't on your object or on MyClass, it will use the fallback.

Jason Goemaat
  • 28,692
  • 15
  • 86
  • 113
  • Hey Jason. That was my first approach but I didn't want to manually do it to keep the code clean. The above response form Egor was what I expected, it worked just fine. Thanks. – Webert Lima Jul 10 '18 at 13:51
  • A little hard to follow :) I had to try it out, so `self.__index = self` is basically `MyClass.__index = MyClass`, which you could move out of the new() method so it's only executed once. When you do `obj.sayHi()`, it uses `MyClass.__index` because it is the metatable for `obj` and picks up `MyClass.sayHi`. When you try something else it does the same, but since it isn't found on MyClass either, it uses `MyClass`'s metatable's `__index` which is `MyClassMeta`... I was thinking 'self' there would be `MyClass`, but it's `obj`... Neat – Jason Goemaat Jul 10 '18 at 21:57