Consider the following code:
#!/usr/bin/lua
local field =
{
name = '',
array = {},
new = function(self, o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end,
}
local fld1 = field:new()
local fld2 = field:new()
fld1.name = 'hello'
table.insert(fld1.array, 1)
table.insert(fld1.array, 2)
table.insert(fld1.array, 3)
fld2.name = 'me'
table.insert(fld2.array, 4)
table.insert(fld2.array, 5)
print('fld1: name='..fld1.name..' len='..#fld1.array)
print('fld2: name='..fld2.name..' len='..#fld2.array)
The output when executed is as follows:
fld1: name=hello len=5
fld2: name=me len=5
From the output, it can be seen that name has different values in fld1 and fld2. array, however, has the same value in fld1 and fld2 (fld1.array and fld2.array are the same and therefore have the same length of 5).
How do I fix this code so that fld1.array is independent of fld2.array (so that modifying fld1.array does not change fld2.array)?
First off, fld1 and fld2 have distinct names because you gave them distinct names - their own properties.
When you perform table key assignment the new key and value are stored directly in the table that you specify. The __index metamethod only comes in to play when you perform table key lookup and the key is not found in the table.
A quick example where we can see that table key assignment will shadow keys in the __index lookup chain.
local Foo = { shared = 'shared', private = 'private' }
Foo.__index = Foo
local foo = setmetatable({}, Foo)
foo.private = 'a shadowed value'
print(Foo.shared, foo.shared) -- shared shared
print(Foo.private, foo.private) -- private a shadowed value
Note: there is a __newindex metamethod for catching table key assignment that involves a never-before-seen key.
Consider treating the new method more like a traditional constructor function, wherein you assign 'private' properties to newly created instances.
local Field = {
-- shared properties go here
}
-- shared methods are defined as such
function Field:new (name)
local o = {
-- private properties for the newly created object go here
name = name or '',
array = {}
}
self.__index = self
return setmetatable(o, self)
end
function Field:insert (value)
table.insert(self.array, value)
end
local fld1 = Field:new('hello')
local fld2 = Field:new('me')
fld1:insert(1)
fld1:insert(2)
fld1:insert(3)
fld2:insert(4)
fld2:insert(5)
print('fld1: name='..fld1.name..' len='..#fld1.array) -- fld1: name=hello len=3
print('fld2: name='..fld2.name..' len='..#fld2.array) -- fld2: name=me len=2
Some more notes:
Generally, 'class' names should be in PascalCase, to make them distinct.
Having a :new function in the lookup chain means instances can invoke it, this may or may not be desirable. (Can create slightly ugly inheritance this way.)
There is a difference between how you define methods which use the implicit self. You should give Chapter 16 of Programming in Lua a read. It might be a tad dated if you're using 5.2 or 5.3, but it should still have a lots of useful information.
If you're interested in a tiny library for this, I recently wrote Base. If you look around, you'll find lots of little OOP packages for making these kinds of things a bit easier.
Lua's oop technic not same c++, java
__index metamethod reference target table record.
and so, fld1, fld2 array item is Field's array item.
you must make a new table and use it.
why name's value different. Because, Your assign a name
fld1.name = 'hello'
simply code fix
fld1.array = {}
using new method code fix
local o1 = {name = '', array = {}}
local o2 = {name = '', array = {}}
local fld1 = field:new(o1)
local fld2 = field:new(o2)
make new table(make o) and insert new method.
Related
I recently wrote some code like this:
function enum(tbl)
local length = #tbl
for i = 1, length do
local v = tbl[i]
tbl[v] = i
end
return tbl
end
eItemType = enum
{
"wpn",
"outf",
"helm",
"art",
"boost",
"bkpk",
"dev",
"ammo",
"none"
}
It works. But I would like to simplify it to this form:
enum eItemType
{
"wpn",
"outf",
"helm",
"art",
"boost",
"bkpk",
"dev",
"ammo",
"none"
}
For the enum function to create a global variable eItemType in the file from which it is called.
I don't know how to implement this (convert eItemType to string in string code).
Functions from the debug library come to mind, namely getline, maybe it can handle it...
Enumerations in Lua
First of all: You're shoehorning a foreign language concept into Lua, which will necessarily not be round on the corners. Languages like C use numbers (integers) for enums because of their efficiency: Integer comparison is fast. In the end, enums are just synctactic sugar for enumerating integer constants though. You don't need any of this in Lua: Lua has string interning, which means strings are only stored once in memory and can be compared as fast as numbers. That is, the naive way to implement enums in Lua, is to simply use strings right away!
local enum_values = {"foo", "bar", "baz"}
local function get_random_enum_value()
return enum_values[math.random(#enum_values)]
end
if get_random_enum_value() == "foo" then
print("Congratulations! The value is foo!")
end
The downside is that you now don't have any table holding your enum values, so you'll either want to brush up on your documentation or create a (redundant) hash table to hold your enum values (which really is just making things slower, but may help readability). You don't need to involve any numbers. If I inspect a table and see a "foo_something" string there, that's a lot more useful than 42.
local my_enum = {foo = "foo", bar = "bar", baz = "baz"}
local enum_values = {my_enum.foo, my_enum.bar, my_enum.baz}
local function get_random_enum_value()
return enum_values[math.random(#enum_values)]
end
-- May be considered more readable since we now have a scope for "foo"
if get_random_enum_value() == my_enum.foo then
print("Congratulations! The value is foo!")
end
then you'd probably write yourself a simple helper to generate these kinds of tables:
function enum(namelist)
local t = {}
for _, v in pairs(namelist) do
t[v] = v
end
return t
end
my_enum = enum{
"foo",
"bar",
"baz",
}
Syntactic Sugar
What you want is called syntactic sugar and yes, it requires Lua's metaprogramming capabilities. If you really want an enum keyword, you'll have to extend Lua or implement a preprocessor adding such a keyword.
First, let's see how Lua sees your current code:
someName = enum { ... }
parses as "call the variable called enum with the table { ... } and assign the result to the variable called someName".
enum someName { ... }
does not parse: this is just a name, followed by... another name? Syntax error. By slightly abridging this syntax, it is however still possible to turn this into a valid expression. How about passing someName as a string to enum, which then returns a function to apply to your table?
enum "someName" { ... }
this would be implemented as
function enum(name)
return function(t)
_G[name] = original_enum(t)
end
end
where original_enum is your original enum function without the added layer for syntactic sugar. You might want to swap out _G with _ENV or getfenv(2) or similar.
Or for another syntactic sugar, you could also use the indexing operator paired with the __index metamethod:
enum.someName { ... }
which is implemented as:
enum = setmetatable({} --[[proxy table]], {__index = function(_, name)
return function(t)
_G[name] = original_enum(t)
end
})
both of these have in common that they are basically just fancy ways of currying the name of the global variable you want to assign to.
f you need to get a table from a string, then try:
local s = [[ return {
"wpn",
"outf",
"helm",
"art",
"boost",
"bkpk",
"dev",
"ammo",
"none"
}]]
eItemType = (loadstring or load)(s)() -- since Lua 5.2 loadstring has been replaced by load
for k,v in pairs(eItemType) do
print( k,v)
end
if you need a full copy of a simple table , then it is created like this:
enum = function(t)
local tmp = {}
for _,v in ipairs(t) do tmp[#tmp+1]=v end
return tmp
end
eItemType = enum {
"wpn",
"outf",
"helm",
"art",
"boost",
"bkpk",
"dev",
"ammo",
"none"
}
if you need to move the creation of a global table or enumerations to another place, look at the solution through modules
and the last solution is to get a line of code from a simple table, used to write to a file and generate lua code
function dumpValue(obj)
local s = '\n{ '
for k,v in ipairs(obj) do
s = s .. '\n['..k..'] = "' .. v .. '",'
end
return s .. '} '
end
print(dumpValue(eItemType))
I'm a bit confused with how Lua treats object references in function arguments. Consider this example:
local tableA = {name = "A"}
local tableB = {name = "B"}
local tableC = {name = "C"}
local function childA(a, b)
a = tableC
b.name = "This works"
end
local function childB(a, b)
print("a =", a.name) -- expected to print "C"
print("b =", b.name) -- prints "This works" as expected
end
local function parentFunction(a, b)
childA(a, b)
childB(a, b)
end
parentFunction(tableA, tableB)
I would expect that in the childA function the a parameter will be replaced by reference to tableC that will continue to be tableC from now on, but it does not happen. Whereas if I change just a property of such parameter in the b parameter it will affect reads of the object down the line. Why the overwrite is not working like this?
Table values are copied by reference, not by value.
in childA you assign tableC to a. a is local to childA.
a = tableC just adds a second reference to the table tableC refers to.
This has no effect on the actual table nor any other reference to it.
Once childA has returned, a is out of scope so the reference you just added is removed.
Make sure you understand that in local tableA = {name = "A"} you create a table value with the table construtor {} and a local reference tableA to that table value. local a = tableA just creates another reference to that table, not a second table! Then a = tableC re-refers a to the table created here local tableC = {name = "C"}
You basically write another address on a piece of paper. That doesn't affect the house at the original address.
I have an empty table which I want to act as a "gateway" to another set of functions at another location.
tbl = {}
I want to pass called functions from this table to somewhere else as a string:
tbl.someMethod("hello")
I've tried this with limited success.
hand = {
__index = function(tbl, name)
hand[name] = function(...)
passToSomewhere(name, ...)
end
end,
__call = function(tbl, name, ...)
hand[name](...)
end
}
setmetatable(tbl, hand)
tbl.someFunction("hello!", someTbl, someNumber)
How do I forward the undefined function through the table without it throwing errors?
Edit: More detail
I'm trying to define and call a function in a table in one call:
tbl = {}
hand = {
__index = function(tbl, name)
print(name)
tbl[name] = function(...)
print(...)
end
end
}
setmetatable(tbl, hand)
s,e = pcall(tbl.help,"banana","goat")
print(s)
s,e = pcall(tbl.help,"banana","goat")
print(s)
This code does work but the first pcall will throw an error because the function hasn't been defined yet.
Say I wanted to use an library which I know updates quite a lot and keep my script compatible and this library may not be present on my computer. I would like to forward calls to this library across some interface but I still want to be able to call the functions in the same way.
--For example I would like to call this function like this:
someLib.doSomething(name, age, telephone)
--Instead of passing it through another function:
someOtherLib.invoke("someLib.doSomething", name, age, telephone)
Is this possible?
Edit 2:
Thanks #greatwolf !
This is my working test code.
tbl = {}
hand = {
__index = function(tbl, name)
tbl[name] = function(...)
return print(name, ...)
end
return rawget(tbl, name)
end
}
setmetatable(tbl, hand)
tbl.help("banana","goat")
Okay, based on your updated details you want lua to translate this call
someLib.doSomething(name, age, telephone)
into
someOtherLib.invoke("someLib.doSomething", name, age, telephone)
behind the scenes. What you have is almost there, you just need to return the newly created function back:
__index = function(tbl, name)
tbl[name] = function(...)
return someOtherLib.invoke("someLib."..name, ...)
end
-- return tbl[name] works too, I used rawget to indicate
-- no further __index lookup should be done
return rawget(tbl, name)
end
Now, if your someOtherLib is just a table of functions, lhf's suggestion will work too
setmetatable(tbl, {__index = someOtherLib})
Now if your someOtherLib provides someway to get the function you want to call without actually invoking it just yet, __index can relay this without creating extra closure wrappers
__index = function(tbl, name)
tbl[name] = someOtherLib.getFuncByName(name)
return tbl[name]
end
The __call metamethod is not needed here.
Backpack = {Potion = 'backpack',Stack = 'bag',Loot = 'derp', Gold = 'random'}
Backpack[1] ~= 'backpack' -- nope
As you guys can see, I cannot call Backpack[1] since its not a numeral table, how would I generate a table after the construction of Backpack, consisting only of it's values? for example:
Table_to_be_Constructed = {Value of Potion,Value of Stack,Value of Loot,Value of Gold} -- this is what i need
It seems simple but I couldn't find a way to do it.
I need it this way because i will run a numeric loop on Table_to_be_Constructed[i]
To iterate over all the key-value pairs in a table, use the pairs function:
local Table_to_be_Constructed = {}
for key, value in pairs(Backpack) do
table.insert(Table_to_be_Constructed, value)
end
Note: the iteration order is not defined. So, you might want to sort Table_to_be_Constructed afterwards.
By convention, the variable name _ is used to indicate a variable who's value won't be used. So, since you want only the values in the tables, you might write the loop this way instead:
for _, value in pairs(Backpack) do
For the updated question
Backpack has no order (The order in the constructor statement is not preserved.) If you want to add an order to its values when constructing Table_to_be_Constructed, you can do it directly like this:
local Table_to_be_Constructed = {
Backpack.Potion,
Backpack.Stack,
Backpack.Loot,
Backpack.Gold
}
Or indirectly like this:
local items = { 'Potion', 'Stack', 'Loot', 'Gold' }
local Table_to_be_Constructed = {}
for i=1, #items do
Table_to_be_Constructed[i] = Backpack[items[i]]
end
In Lua, you can create a table the following way :
local t = { 1, 2, 3, 4, 5 }
However, I want to create an associative table, I have to do it the following way :
local t = {}
t['foo'] = 1
t['bar'] = 2
The following gives an error :
local t = { 'foo' = 1, 'bar' = 2 }
Is there a way to do it similarly to my first code snippet ?
The correct way to write this is either
local t = { foo = 1, bar = 2}
Or, if the keys in your table are not legal identifiers:
local t = { ["one key"] = 1, ["another key"] = 2}
i belive it works a bit better and understandable if you look at it like this
local tablename = {["key"]="value",
["key1"]="value",
...}
finding a result with : tablename.key=value
Tables as dictionaries
Tables can also be used to store information which is not indexed
numerically, or sequentially, as with arrays. These storage types are
sometimes called dictionaries, associative arrays, hashes, or mapping
types. We'll use the term dictionary where an element pair has a key
and a value. The key is used to set and retrieve a value associated
with it. Note that just like arrays we can use the table[key] = value
format to insert elements into the table. A key need not be a number,
it can be a string, or for that matter, nearly any other Lua object
(except for nil or 0/0). Let's construct a table with some key-value
pairs in it:
> t = { apple="green", orange="orange", banana="yellow" }
> for k,v in pairs(t) do print(k,v) end
apple green
orange orange
banana yellow
from : http://lua-users.org/wiki/TablesTutorial
To initialize associative array which has string keys matched by string values, you should use
local petFamilies = {["Bat"]="Cunning",["Bear"]="Tenacity"};
but not
local petFamilies = {["Bat"]=["Cunning"],["Bear"]=["Tenacity"]};