I make program in lua where I create a class that stores 3 values and a function that can return the "x" value. This is the code:
Vector3 = {}
Vector3.__index = Vector3
function Vector3:new(x,y,z)
local instance =
{
x = x or 0,
y = y or 0,
z = z or 0
}
setmetatable(instance, Vector3)
return instance
end
function Vector3:printX()
return self.x
end
And this is my main.lua code:
require "Vector3"
vec1 = Vector3.new(1,2,3)
vec2 = Vector3.new(5,1,0)
print(vec1:printX())
This what this line does
vec1 = Vector3.new(1,2,3)
Is create a new class and assign 1 to x, 2 to y, and 3 to z. But when I print out the value returned by printX(), it gives me 2. If I change it to print out the y value, it gives me 3. And if I change it to print out the z value, its nil. Is there anything wrong with my code?
I want it to return the correct values.
As tehtmi has pointed out, function Vector3:new(x,y,z)
is equivalent to function Vector3.new(self,x,y,z)
. The call Vector3.new(1,2,3)
thus sets self = 1
, x = 2
, y = 3
and z = nil
; 2
is printed.
There are two ways to fix this:
Vector3.new
, removing the self
parameter.Vector3:new
, passing self = Vector3
. This obsoletes the Vector3
global variable access in the constructor; setmetatable(instance, Vector3)
can then be replaced with setmetatable(instance, self)
.If you rewrite the constructor slightly, you get the standard "class" template outlined in the "Programming in Lua" book:
function Vector3:new(x,y,z)
local instance =
{
x = x or 0,
y = y or 0,
z = z or 0
}
setmetatable(instance, self)
self.__index = self -- note the addition of this line
return instance
end
You can then remove the Vector3.__index = Vector3
line since this will be done in the constructor every time you call Vector3:new(...)
(arguably questionable performance-wise). The advantage of this approach is how it deals with inheritance: Inheritance is implemented simply as instantiation; you do not need to take care of writing a new constructor. Say you want a class Scale
to extend your Vector3
class:
Scale = Vector3:new() -- a scale is a vector ("inherits")
function Scale:scale(vec) -- scale a vector (componentwise multiplication)
return Vector3:new(self.x * vec.x, self.y * vec.y, self.z * vec.z)
end
Usage:
-- Instantiation:
-- This wouldn't work if you used `.`
-- since then `Scale.new` would be just `Vector3.new`
-- which sets `Vector3` and not `Scale` as the metatable,
-- but since the `:` passes `Scale`/`Vector3` as `self`, everything is fine.
local s = Scale:new(1, 2, 3)
local v = s:scale(Vector3.new(4, 5, 6))
print(v.x, v.y, v.z) -- 4, 10, 18
You ultimately have to decide whether you prefer constructors looking up their class through an upvalue or global variable or having it passed as the self
parameter.
As a rule of thumb, if you define a function using :
, you should probably call it using :
. Such functions are often called "methods". See difference between .
and :
in Lua.
Besides, I would rename printX
to getX
or remove it entirely (since it is currently redundant with just directly accessing x
).
Note also that default values are often implemented by sticking them into the class table (in your case Vector3.x, Vector3.y, Vector3.z = 0, 0, 0
would allow you to eliminate the or 0
's in the constructor).