Im trying to understand how metatables work and what object oriented programming actually is. I've encountered something i can't figure out.
Account = {} function Account.new(value) return setmetatable({balance = value}, Account) end Account.__index = Account --THIS LINE function Account:withdraw(v) self.balance = self.balance - v end function Account:deposit(v) self.balance = self.balance + v end function Account:__tostring() return "Account(" .. self.balance .. ")" end b = Account.new(100) b:deposit(10) print(b.balance)
This is copy pasted from the Roblox Wiki. I can't figure out what the 6th line is for, the script doesn't work without it and the error code says "Workspace.Script:21: attempt to call method 'deposit' (a nil value)" so im completely lost. If anyone could explain to me how this script exactly works i'd be grateful.
A quick glance can mislead you into thinking that Account is the object when it is not. Line 4 creates a new object, because setmetatable
returns its first argument. The only defined key in this object is "balance" as you can see here {balance = value}
.
This makes for a very boring table. Thankfully, this table has a metatable. The details of this metatable have been outlined in the surrounding lines. The variable Account
points to this metatable.
Now, our table that we created before can access deposit
and withdraw
only because of line 6. The __index
metamethod by default will make the table search for contents in the metatable itself if a key cannot be found within the table.
Really sorry that you were confused by this. This was a horrible example ripped right out of a page from the Lua website. In reality, your code should separate your constructor, object table, and metatable. This example fails to do that by combining constructor and metatable, as well as throwing some methods that could have just been defined in the object table right in the metatable. People often do this because it is more memory efficient, and to that I laugh because working solely in a scripting language means you have nearly no right or need to fuss over such petty optimization details.
Here's is a cleaned up, and arguably easier to understand, implementation of the sample you shared. I made sure to clearly separate constructor, object table, and metatable so it should be much easier to learn from it.
local Account = { } local Account_Metatable = { __tostring = function(self) return "Account(" .. self.balance .. ")" end } function Account.new(value) local Object = {balance = value} function Object:withdraw(v) self.balance = self.balance - v end function Object:deposit(v) self.balance = self.balance + v end return setmetatable(Object, Account_Metatable) end b = Account.new(100) b:deposit(10) print(b.balance)