3

I'd like to create MyClass class in Lua in a separate file myclass.lua which I can import and use later. It should be working the following way:

local MyClass = require 'myclass'
tab = {1,2,3}
m = MyClass(tab)

However, following the code in Lua docs I can't make it work and am getting errors attempt to call global 'MyClass' (a table value).

The code I have written so far for myclass.lua:

local MyClass = {}
MyClass.__index = MyClass

function MyClass.__init(tab)
    self.tab = tab or {}
    setmetatable({},MyClass)
    return self
end
return MyClass

There is a plethora of examples how to write classes in Lua but I don't think I understand the difference and as a result getting lost in the implementation details. Is there a more or less conventional way to do it?

minerals
  • 6,090
  • 17
  • 62
  • 107

2 Answers2

3

In Lua, you cannot usually call a table like you would call a function. For example, this code will produce an error of "attempt to call local 't' (a table value)".

local t = {}
t()

There is a way of making this work by using metatables, however.

local hello = {}
local mt = {} -- The metatable
mt.__call = function ()
  print("Hello!")
end
setmetatable(hello, mt)
hello() -- prints "Hello!"

When you try and call a table as you would a function, Lua first checks to see whether the table has a metatable. If it does, then it tries to call the function in the __call property of that metatable. The first argument to the __call function is the table itself, and subsequent arguments are the arguments that were passed when the table was called as a function. If the table doesn't have a metatable, or the metatable doesn't have a __call function, then an "attempt to call local 't'" error is raised.

Your example code has three problems:

  1. You are trying to use __init instead of __call. Lua doesn't have an __init metamethod.
  2. __call takes different parameters than the ones you are using. The first parameter to the __call function is the table itself. You can either use function MyClass.__call(self, tab), or use the colon syntax, function MyClass:__call(tab), which implicitly adds the self parameter for you. These two syntaxes are functionally identical.
  3. You haven't set a metatable for the MyClass table. While you are setting a metatable for MyClass's objects, that doesn't mean that a metatable is automatically set for MyClass itself.

To fix this, you could do something like the following:

local MyClass = {}
setmetatable(MyClass, MyClass)
MyClass.__index = MyClass

function MyClass:__call(tab)
    local obj = {}
    obj.tab = tab or {}
    setmetatable(obj, MyClass)
    return obj
end

return MyClass

This sets MyClass to use itself as a metatable, which is perfectly valid Lua.

The system of metatables is very flexible, and allows you to have just about any class/object scheme you want. For example, if you want, you can do everything inline.

local MyClass = {}

setmetatable(MyClass, {
    __call = function (class, tab)
        local obj = {}
        obj.tab = tab or {}
        setmetatable(obj, {
            __index = MyClass
        })
        return obj
    end
})

return MyClass

As well as being concise, this also has the advantage that people can't change the class's metamethods if they have access to the class table.

Jack Taylor
  • 5,588
  • 19
  • 35
  • 1
    That's a slight disadvantage (ok, a major disadvantage) with setting a table's metatable as itself, and setting its __index metamethod to itself. `local obj = MyClass{1, 2, 3}` actually works fine with this code, but then if you try to index `obj.foo`, then it sees that `foo` doesn't exist in `obj`, and then checks the `MyClass` table for `foo` as that's what you set the __index of obj to. It doesn't find `foo` in `MyClass` either, so it looks at `MyClass`'s __index, which is... `MyClass`. Lua is smart enough to detect that this is an infinite loop, so it errors out for you. – Jack Taylor May 23 '17 at 08:52
0

There is no __init metamethod available for a table. When you do the following:

m = MyClass(tab)

it looks for the MyClass.__call method definition. Just update your myclass.lua as:

local MyClass = {}
MyClass.__index = MyClass

function MyClass:__call(tab)
    self.tab = tab or {}
    setmetatable({},MyClass)
    return self
end

return MyClass
hjpotter92
  • 78,589
  • 36
  • 144
  • 183
  • Yes, but it does not solve the problem with error. Try to import this and call `local m = MyClass({1,2,3})`. – minerals May 22 '17 at 19:40
  • 2
    @minerals First argument to `__call` is the "function" that was called – in this case, the class. So change it to `function MyClass.__call( _, tab )` here. (If you happen to have "generic" constructors later on, you can give '`_`' a different name & use it to find out which class `__call` should be constructing an instance of.) – nobody May 22 '17 at 20:54
  • It does not work. I am getting `attempt to call global MyClass` errors. – minerals May 22 '17 at 21:23
  • @nobody ah, completely forgot about self reference :/ Edited now – hjpotter92 May 23 '17 at 09:30