oopluaconstructorfactory

self inside Lua constructors


Sorry it's not quite a specific question.

In the "Programming in Lua, 4th edition" one can see the following code :

Account = {balance = 0}

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

My confusion is the line

self.__index = self

This means that Account becomes its own __index only on creation of an instance :

a = Account:new()

which is confusing if you think about it. I know that it is done to easily increase the depth of inheritance so you can do :

AccountGold = Account:new({cashBackPercentage = 5})
AccountPlatinum = AccountGold:new({personalSupport = true})

but to me this

Account.__index = Account

looks more readable.

Even more confusing. For example, look at the yui library:

local Button = setmetatable({
    __call = function(cls, args) return cls:new(args) end
}, Widget)
Button.__index = Button


function Button:new(args)
    self = setmetatable(args, self)

    self.text = self.text or ""
    self.align = self.align or 'center'
    self.valign = self.valign or 'center'
    self.active = false
    if not self.notranslate then
        self.text = T(self.text)
    end
    return self
end

Look at this :

self = setmetatable(args, self)

which reads to :

Button = setmetatable(args, Button)

which means that every time new instance is created the Button table is changed.

Is it like a common practice ? Could you comment on this please ?


Solution

  • What you read is incorrect, self = setmetatable(args, self) doesn't change Button, it just overwrites the parameter self to the return value of setmetatable, so it is equivalent to the following code:

    local t = setmetatable(args, self)
    t.text = t.text or ""
    ...
    

    The implicit parameter self is dynamic, its value depends on how the function is invoked. For Account:new(....), self is Account, for AccountGold:new(....), self is AccountGold, so that you can achieve inheritance with a single function.

    self.__index = self is cheap, but if you need to strictly control the exposed members, you should create a separate metatable.