Define __index inside prototype "constructor" or outside

Viewed 57

I was reading the chapter "Object-Oriented Programming" in "Programming in Lua" on https://www.lua.org/pil/16.html.

In that example, they create this "constructor":

Account = {balance = 0}
function Account:new (o)
  o = o or {}   -- create object if user does not provide one
  setmetatable(o, self)
  self.__index = self
  return o
end

I've made some "sintetic benchmarks" on Linux with a simple time in a script with 10 million operations like these one. Defining Account.__index = Account outside the table initialization is 200ms faster.

My question is, why set the self.__index inside this function that will be executed every time this function is called if we can define outside and execute a single time? Heritage maybe?

EDITED:

Thank you luther for the answer, I'll just create an example here for everyone with this doubt:

local a = {}
a.__index = a
function a:foo()
    return 'foo'
end

function a:new(o)
    print(self)
    o = o or {}
    setmetatable(o, self)
    -- self.__index = self
    return o
end

local b = a:new()
-- b.__index = b
function b:bar()
    return 'bar'
end

local z = a:new()
print(z:foo()) -- this will work

local y = b:new()
print(y:foo()) -- attempt to call method 'foo' (a nil value)
print(y:bar()) -- attempt to call method 'bar' (a nil value)

Of course y will have a metatable with b as that table, but b doesn't have an __index entry, this entry is only inside b's metatable. If you still want to avoid declaring __index inside the "constructor", you'll need specify this in every derivated prototype, or "subclass".

1 Answers

The author of PiL seems to be trying to simplify things by having the new method take care of the root object the same way it takes care of all the child objects. This can be confusing to beginners, because it's not immediately clear that self.__index = self is often redundant.

Also, it's actually faster to do it this way than to add an __index to every object. Remember that in a prototype system, every object is potentially a prototype for other objects. On my machine, with 1e8 trials, the PiL way takes 14 s, while adding __index to all objects takes 23 s. A new key means the table has to grow, so it's slower than assigning to a key that already exists.

Confusingly, this PiL section is titled "Classes", but in the first paragraph, he says he's emulating prototype-based languages, where "objects have no classes." This screws up the reader's expectations even further. This section implements a self-replicating object, not a class.

Here's my less confusing, but slower implementation:

Account = {balance = 0}
Account.__index = Account

function Account:new (o)
  o = o or {}   -- create object if user does not provide one
  setmetatable(o, self)
  o.__index = o
  return o
end
Related