2
votes

As I understand it, there are multiple ways to define a member function to a table in Lua. For example, the following two appear to be equivalent:

-- method a)
local table1 = {x = 1, y = 2}
function table1:myfunc()
    return self.x + self.y
end

-- method b)
local table2 = {
    x = 1,
    y = 2,

    myfunc = function(self)
        return self.x + self.y
    end,
}

Having previously used Python, my instinct would be to use method b) to keep things more neatly grouped together. However reading through examples it seems that people generally use method a) by convention. I can't find any objective reason for why it should be superior though.

Indeed, it appears that on the contrary there are reasons to at least forward-declare the function variable when you initialise the table, like so:

local table3 = {x = 1, y = 2, myfunc}
function table3:myfunc()
    return self.x + self.y
end

This way Lua knows about the existence of the member from the beginning and can set up the hashes correctly, whereas increasing the number of members to an existing table may require a rehash (although I can't imagine this ever actually becoming a noticable performance problem unless maybe you do this for a huge number of small tables). For sources compare: https://www.lua.org/gems/sample.pdf

So is there any reason for not defining member functions directly during the definition of the table itself? Or is it just the fact that some syntactic sugar (the function name() syntax and the colon) is not available then?

1
The table3 example doesn't do what you think it does. The table constructor {} will copy the values to be inserted, reading the variable myfunc and storing it under index [3]. It will not create the myfunc entry to be filled later. If you wanted to create that entry, then write myfunc = true, to assign something that is not nil. But frankly, you won't see any speedup here, just will waste more time writing unneeded text. Also note that it's more maintainable if you store all the method functions in separate table, and set it as metatable to all objects of the same type.Vlad
Method a allows you to access variable table1 as upvalue from inside your functions. Method b doesn't allow this.Egor Skriptunoff
@Vlad Ah right, I was wondering how it should be able to differentiate between a forward declaration and any old undeclared variable. And seeing as table.member is equivalent to table["member"] in Lua, I guess table3 = {1 = 1, y=2, "myfunc"} would also do the trick, right?Abun
@EgorSkriptunoff Fair enough, but wouldn’t passing self achieve the same thing?Abun
@Vlad Thinking about it again, it probably wouldn’t do the trick because Lua would still store the string under index 3…Abun

1 Answers

2
votes

As a functional programming nerd, I would prefer method b, because it defines the table all at once instead of mutating it once for every method we need to add. One thing you should be aware of is that some methods may call each other, and it's faster to use locals than to call self:myfunc().

local function myfunc(self)
  return self.x + self.y
end

local myTable = {
  x = 1,
  y = 2,
  myfunc = myfunc,

  moreMyfunc = function(self)
    return myfunc(self) * 2
  end,
}

If the lack of symmetry between methods is bothersome, you can make all methods local and add them in the table constructor, like I just did with myfunc.

Egor brought up another important point (which is more relevant if the table is a class):

Method a allows you to access variable table1 as upvalue from inside your functions. Method b doesn't allow this.

In this case, I would forward declare the table name. This has the advantage of changing a variable once, instead of mutating the table many times.

local myTable

local function myfunc(self)
  return self.x + self.y
end

local function moreMyfunc(self)
  return myfunc(self) * 2
end

myTable = {
  x = 1,
  y = 2,
  myfunc = myfunc,
  moreMyfunc = moreMyfunc,
}