Search code examples
inheritanceluasingleton

Lua inheritance example creates singular instance


Let's try out the inheritance example from the official lua docs and declare everything this way:

Account = {
  balance = 50,
  receipt = function(self)
    print(self.balance)
  end,

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

SpecialAccount = {
  balance = 150,  
  special_receipt = function(self)
    print(self.balance)
    print("This is special!")
  end,  
}

-- Test ---------------------------
s = Account:new(SpecialAccount)

print(tostring(Account))
print(tostring(SpecialAccount))
print(tostring(s))

s:receipt()
s:special_receipt()

This is nice and the receipt-functions work as expected, but there is a problem with s:

console output It looks like s is SpecialAccount.

They are the same tables. This is bad, because now you can not create several instances of SpecialAccount. What am I missing here?


Solution

  • It looks like s is SpecialAccount. They are the same tables.

    Yes, s is SpecialAccount: Account:new(SpecialAccount) makes SpecialAccount extend Account, returning SpecialAccount (this should become apparent if you go through what :new does and how the : syntactic sugar works).

    In Pseudocode, this might look as follows in a language supporting class-based OOP:

    class Account { ... } // Account = ...
    class SpecialAccount { ... } // SpecialAccount = ...
    SpecialAccount extends Account; // s = Account:new(SpecialAccount)
    

    Why return SpecialAccount you may wonder? To make it read more naturally, as in a class-based language:

    class SpecialAccount extends Account { ... }
    

    you would write:

    SpecialAccount = Account:new{
      balance = 150,  
      special_receipt = function(self)
        print(self.balance)
        print("This is special!")
      end,  
    }
    

    It may be considered dirty to "overload" :new both for instantiation and inheritance, but if you look closely, inheritance can be seen as a special form of instantiation.

    This is bad, because now you can not create several instances of SpecialAccount

    This is incorrect. You can create an instance of SpecialAccount just like you can create an instance of Account: By calling :new:

    s = SpecialAccount:new() -- creates a new special account
    assert(s ~= SpecialAccount)
    

    you can also pass a table with some values if you like:

    s = SpecialAccount:new{balance = 50}