I have a situation like this:
TestClass = { param = { n = 5 } }
function TestClass:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end
function TestClass:update(n)
n = n or 1
self.param.n = self.param.n + n
end
The issue is that when I instantiate an object like: obj = TestClass:new() the update method doesn't update the value stored in the obj table but instead it changes the value inside the default table: TestClass
Is there a way to fix this? I already looked up the tutorial by the creators of lua but they say nothing about tables as attributes.
I think you should do it like this then:
function TestClass:new(o)
o = o or { param = { n = 5 } }
setmetatable(o, self)
self.__index = self
return o
end
I solved this and the problem was that tables are stored by reference, not value so the solution was to create the table inside the o object at the time of instancing (the TestClass:new() method).
To do this automatically and only for params that are not passed in I wrote this little function:
function setDefaults(o, def)
for k,v in pairs(def) do
o[k] = o[k] or v
end
end
And it is simply used like this:
function TestClass:new(o)
o = o or {}
setDefaults(o, {
param = { n = 5 }
})
setmetatable(o, self)
self.__index = self
return o
end
I also wanted to thank a lot u/JackMacWindowsLinux and Ivo Beckers for helping me find this solution.
Related
The following code should print 'hello', however it is printing the memory location of the table (i.e. 'table: 052E67D0'). Please, explain what I'm missing here.
TestClass = {}
function TestClass:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end
function TestClass:__tostring()
return "hello"
end
local t = TestClass.new{}
print(t)
Update
Tried doing this instead:
TestClass = {}
function TestClass:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
self.__tostring = function() return "hello" end
return o
end
local t = TestClass.new{}
print(t)
which worked. This seems weird because, to me, self in constructor and TestClass: refer to the same table.
Your TestClass:new takes two arguments and you call it with just one when you create t.
Change:
local t = TestClass.new{}
to:
local t = TestClass:new{}
Thanks to that self in this TestClass:new call is now reference to TestClass rather than to empty table which was (most likely) meant to be the new instance of the class.
In case of doubts please refer to Lua Reference Manual ยง3.4.10 or this stackoverflow question.
I'm new to Lua, and I'm trying to understand its OO part, for example :
lkw = {}
lkw.la= 0
function lkw:func(ge)
self.la = self.la + ge
end
function lkw:new()
local res = {}
setmetatable(res, self)
self.__index = self
return res
end
mylkw = lkw:new()
in this example the "class" lkw can create object using new, but what do self and index mean ?
should consider self as this in java/C++ and what is the index ?
This style of OOP is frequent in Lua. I do not like it because it is not explicit enough for me, but let me try to explain.
There are two confusing things: the use of the : sugar in function definitions and the use of the "class" as the metatable for its instances.
First, function a:b(...) is the same as a.b = function(self, ...), so let us remove all sugar:
lkw = {}
lkw.la = 0
lkw.func = function(self, ge)
self.la = self.la + ge
end
lkw.new = function(self)
local res = {}
setmetatable(res, self)
self.__index = self
return res
end
mylkw = lkw.new(lkw)
Now, this is "prototypal inheritance". lkw is the "prototype" for instances like mylkw. This is similar but slightly different from a "class".
When the new constructor is called, lkw is passed as the self argument.
The second and third lines of the constructor are weird. This is probably easier to understand:
lkw.new = function(self)
local res = {}
setmetatable(res, {__index = lkw})
return res
end
i.e.: if we do not find something in the instance we go look for it inside the prototype.
This explains how func works. The first time it is called, the instance will not contain a la key so lkw.la will be used.
The reason the code is not written this way is that the weird construction allows "prototypal inheritance": you could call "new" on mylkw and get an "instance of the instance" (i.e. in prototypal inheritance an instance and a child class are the same thing).
I think this is a very confusing feature. For reference this is about how I would write code that does about the same thing, with no inheritance:
local methods = {
func = function(self, ge)
self.la = self.la + ge
end
}
local lkw = {
new = function()
return setmetatable({la = 0}, {__index = methods})
end
}
local mylkw = lkw.new()
got some problem with metatable. This is my simple metatable:
local mt = {}
function mt:add(n)
return setmetatable({n = n}, {__index = mt})
end
function mt:get() return self.n end
Now I want to add some division like:
mt.math
mt.effect
Which each one has some own methods like:
mt.math:floor() return math.floor(self:get()) end
mt.effect:show(args) onMapShowEffect(self:get(), {x = x + (args[1] ~= nil or 0), ...) end
mt.effect:get() return getCurrentPos() end
Any ideas?
OK, trying make all details to share my problem.
Player = {}
function Player:add(this)
return setmetatable({this = this}, {__index = Player})
end
Player:get() return self.this end
Above code works perfectly on this example
function enterToGame(player1, player2)
local p1 = Player:add(player1)
local p2 = Player:add(player2)
print(p1:get()) -- ID1
print(p2:get()) -- ID2
Now I want to create some helpfully methods(functions) for table Player. I want to make it more flexible, so I want divide it for classes. Example:
Player.info = {
id = function() return Player:get() end,
}
Player.pos = {
get = function() return getPosition(Player:get()) end,
set = function(args) setPosition(Player:get(), args) end,
}
Player.speed = {
get = function() return getSpeed(Player:get()) end,
set = function(value) setSpeed(value) end,
improve = function(value) setSpeed(Player.speed.get() + value) end,
}
But its not work exactly what I want:
function enterToGame(player1, player2)
local p1 = Player:add(player1)
local p2 = Player:add(player2)
print(p1:get()) -- ID1
print(p2:get()) -- ID2
print(p1.info.id()) -- ID2 instead of ID1
print(p2.info.id()) -- ID2
When I put Player:get() in my methods its return last object declaration.
Based on what you state, if you do
mt.math = mt:add(123)
You don't need themt:get() because mt is the metatable for mt.math. Then
mt.math.floor = function(self) return math.floor(self.n) end
will work as expected. For example,
print(mt.math:floor())
prints 123.
EDIT 1: So now that I have a better understanding of what you are trying to do: normally you would do
p1:id()
p1:getPos()
p1:setPos()
p1:getSpeed()
p1:improveSpeed()
Note the colon, this is important, so that each method gets a "self" as first parameter, thereby given them the table instance to operate on (p1, in the above example). Instead you want to group methods so
p1.info:id()
p1.pos:get()
p1.pos:set()
p1.speed:improve()
p1.speed:get()
These methods will get a self that points to p1.info, p1.pos, etc. But those sub-tables have no knowledge of the container table (p1). The info and pos tables are in the Player class: they are shared by all instances of Player (p1, p2 etc). You have to make the info and pos tables non-shared:
function Player:add(player)
local pN= setmetatable( {n = player, info={}, pos={}}, {__index = Player})
pN.info.id = function() return pN.n end
pN.pos.set = function(x) return setPosition(pN, x) end
return pN
end
Then you get
> p1=mt:add(player1)
> p2=mt:add(player2)
> print(player1)
table: 0024D390
> print(p1.info.id())
table: 0024D390
> print(player2)
table: 0024D250
> print(p2.info.id())
table: 0024D250
All that said, I don't really like the idea of having to use closures like this, perhaps there are gotchas since not everything will be in Player.
Is there a way that I can convert a hierarchy string into table form?
Suppose the input is A.B.C.D
ouput should be a table which traverses above input:
A = {}
A.B = {}
A.B.C = {}
A.B.C.D = {}
Thanks.
The obvious solution would be to parse the string up and construct the hierarchy table from that. But a more clever solution is to let lua do it for you. With a bit of metamagic and function environment manipulation this can be done:
dump = require 'pl.pretty'.dump -- convenient table dumper from penlight
function createtable(str)
local env_mt = {}
env_mt.__index = function(t, k)
rawset(t, k, setmetatable({}, env_mt))
return rawget(t, k)
end
local env = setmetatable({}, env_mt)
local f = loadstring("return "..str)
setfenv(f, env)
f()
return env
end
dump( createtable "A.B.C.D" )
this outputs:
{
A = {
B = {
C = {
D = {
}
}
}
}
}
#greatwolf's answer is right but I prefer the more straightforward approach of "parsing" the string and constructing the table. Less magic, and you do not execute a function loaded from a (possibly) user-defined string, which would be a security issue.
local createtable = function(str)
local top = {}
local cur = top
for i in str:gmatch("[^.]+") do
cur[i] = {}
cur = cur[i]
end
return top
end
(require "pl.pretty").dump(createtable("A.B.C.D"))
In the code below it's my attempt to nest metatables on __index, but it's not working. What I want to do is if the value is t1 or t2 then return the associated value, otherwise call the function on the innermost __index. Is this possible?
so in the below x["hello"] can I return a value. I know I can just use a function on the outermost __index, but it seems I should be able to do this somehow using nested metatables.
tia.
x = { val = 3 } -- our object
mt = {
__index = {
t1 = 2,
t2 = 3,
__index = function (table, key)
print("here"..key)
return table.val
end
}
}
setmetatable(x, mt)
print(x["t1"]) -- prints 2
print(x.t2) -- prints 3
print(x["hello"]) -- prints nil???
This works but it seems like I could do it with metatables
x = { val = 3 } -- our object
mt1 = {
t1 = 2,
t2 = 3
}
mt = {
__index = function (table, key)
local a = mt1[key]
if a == nil then
print("here"..key)
a = "test"
end
return a
end
}
setmetatable(x, mt)
print(x["t1"])
print(x.t2)
print(x["hello"])
..and for anyone following along at home, here it is with nested metatables inline. Thanks for the tip Alexander that makes it a lot cleaner.
x = setmetatable(
{ val = 3 },
{
__index = setmetatable({
t1 = 2,
t2 = 3
},
{
__index = function (table, key)
print("here"..key)
return key
end
}
)
}
)
print(x["t1"])
print(x.t2)
print(x["hello"])
This works, but can I do it without declaring mt2?
x = { val = 3 } -- our object
mt2 = {
__index = function (table, key)
print("here"..key)
return key
end
}
mt = {
__index = {
t1 = 2,
t2 = 3
}
}
setmetatable(mt.__index, mt2)
setmetatable(x, mt)
print(x["t1"])
print(x.t2)
print(x["hello"])