local t = {}
local mt = setmetatable({
-- some meta method to know when a key is added or lost and prints a message
}, t)
Is there a way of doing this. I talked about this with someone and they said i couldn't just do it with meta methods but proxies as well. I'm a bit stumped on how to make this work. Can anyone help?
Thanks
To track table keys in lua there are 2 most importaint keys in metatable: __index and __newindex.
__newindex is used to create new key in the table if such key is not found. __index is used to get the value if there is no such key in table.
With __newindex it is possible to track creation, but not assignment, so it is not possible to track key removal:
<script src="https://github.com/fengari-lua/fengari-web/releases/download/v0.1.4/fengari-web.js"></script>
<script type="application/lua">
local t={}
setmetatable(t, {
__newindex = function(self, key, value)
print('Added Key:'..key,'Value:'..value)
rawset(self, key, value)
end
})
t.test = 'test'
t.test = nil -- delete not tracked
t.test = 'test2'
</script>
Using proxy table and __newindex with __index we can track every assignment:
<script src="https://github.com/fengari-lua/fengari-web/releases/download/v0.1.4/fengari-web.js"></script>
<script type="application/lua">
local t={}
local proxytable={}
setmetatable(t, {
__newindex = function(self, key, value)
if proxytable[key] then
if value == nil then
print('Deleted Key:'..key)
else
print('Changed Key:'..key,'Value:'..value)
end
else
print('Added Key:'..key,'Value:'..value)
end
rawset(proxytable, key, value)
end,
__index = proxytable
})
t.test = 'test'
t.test = nil
t.test = 'test2'
t.test = 'test3'
t.test = nil
</script>
If you want enumerate table keys with pairs(), ipairs(), then you need to use metakeys __pairs and __ipairs as original table is allways empty.
local tab = {}
local meta = {}
setmetatable( tab,
{ __newindex = function( self, key, value )
print( key, value )
rawset( self, key, value )
end
} )
tab [1] = 'this'
tab [#tab +1] = 'that'
tab .the = 'other'
tab [3] = nil
tab [4] = 2
I want to inherit method "GetName" or other methods from "Create" for "CreateInherited" and i want save unique methods from "CreateInherited" (like "GetInheritName"), but i dont know how.
My test code:
local MainTbl = {}
function MainTbl:Create(name)
local tbl = {}
tbl.name = name or 'Null'
function tbl:GetName()
return self.name
end
setmetatable(tbl, self)
self.__index = self
return tbl
end
function MainTbl:CreateInherited(name)
local tbl = {}
tbl.name = name or 'Null'
function tbl:GetInheritName()
return self.name
end
setmetatable(tbl, self)
self.__index = self
return tbl
end
local Man = MainTbl:Create('Man')
local Woman = MainTbl:CreateInherited('Woman')
print(Man:GetName())
print(Woman:GetName())
print(Woman:GetInheritName())
If I understand you, you're trying to put two different constructors into a single class. Notice that Create does most of the work that CreateInherited needs to do, so you can save yourself a lot of repeated code by calling Create inside CreateInherited. Instead of starting with an empty table, you can start with a fully formed instance from Create and add a method to it.
function MainTbl:CreateInherited(name)
local tbl = self:Create(name)
function tbl:GetInheritName()
return self.name
end
return tbl
end
I'm looking at solutions to add garbage collection to my tables (objects) in Lua 5.1. I have found that this can be worked around using newproxy() and __gc:
Lua 5.1 workaround for __gc metamethod for tables
https://github.com/katlogic/__gc
What I don't understand is the author's use of inserting the userdata as a field in the table.
All objects you set a metatable on through this wrapper get "polluted" with special key __gc_proxy (can be any string, user definable through __GC_PROXY global). You'll have to special-case it if you iterate over the fields of tables (next(), pairs() ...).
and
There is one thing to concern while using suggested solution - if you will traverse the table by pairs() you will get one addition key. It is possibly to avoid it by using proxy object with proper metamethods in place of original table.
Here is a copy/paste example from the Stack Overflow thread:
function setmt__gc(t, mt)
local prox = newproxy(true)
getmetatable(prox).__gc = function() mt.__gc(t) end
t[prox] = true
return setmetatable(t, mt)
end
iscollected = false
function gctest(self)
iscollected = true
print("cleaning up:", self)
end
test = setmt__gc({}, {__gc = gctest})
collectgarbage()
assert(not iscollected)
for k, v in pairs(test) do
print(tostring(k) .. " " .. tostring(v))
end
The output is:
userdata: 0003BEB0 true
cleaning up: table: 00039D58
But this cleanup is from the script ending and not at the call of collectgarbage().
This can be demonstrated by a slightly modified version that ends in a loop. The output should be "cleaning up":
function setmt__gc(t, mt)
local prox = newproxy(true)
getmetatable(prox).__gc = function() mt.__gc(t) end
t[prox] = true
return setmetatable(t, mt)
end
function gctest(self)
print("cleaning up:", self)
io.flush()
end
test = setmt__gc({}, {__gc = gctest})
collectgarbage()
while (true) do
end
Instead, by removing the offending t[prox] = true, the collection works as expected:
function setmt__gc(t, mt)
local prox = newproxy(true)
getmetatable(prox).__gc = function() mt.__gc(t) end
t[prox] = true
return setmetatable(t, mt)
end
function gctest(self)
print("cleaning up")
io.flush()
end
test = setmt__gc({}, {__gc = gctest})
collectgarbage()
while (true) do
end
Output:
cleaning up
As I understand lua don't call __index unless the key wasn't found in the table so I have that code and it suffers from infinite recursion in the __index part which I don't get as both values used inside the __index function already exist in the table!?
This is basically a test script for trying to save the size of the table in a memory to retreive when # is called
do
local lenKey,originalKey = {},{}
fastKey = {}
fastKey.__len = function(t) return t[lenKey] end
fastKey.__index = function (t,k)
t[lenKey] = t[lenKey] +1
return t[oroginalKey][k]
end
fastKey.__newindex = function(t,k,v) t[originalKey][k] = v end
fastKey.__pairs = function ()
return function (t, k)
return next(t[oroginalKey], k)
end
end
function fastLen(t)
local proxy = {}
local c = 0
for _ in pairs(t) do
c=c+1
end
proxy[lenKey] = c
proxy[originalKey] = t
setmetatable(proxy,fastKey)
return proxy
end
end
n = fastLen{1,2,3,x=5}
--n:insert(1) -- here the __index is called and gets stackoverflow
print(#n)
You've got two typos in there: both the __index and __pairs functions contain oroginalKey instead of originalKey.
I'm trying, as an exercise, to make a set implementation in Lua. Specifically I want to take the simplistic set implementation of Pil2 11.5 and grow it up to include the ability to insert values, delete values, etc.
Now the obvious way to do this (and the way that works) is this:
Set = {}
function Set.new(l)
local s = {}
for _, v in ipairs(l) do
s[v] = true
end
return s
end
function Set.insert(s, v)
s[v] = true
end
ts = Set.new {1,2,3,4,5}
Set.insert(ts, 5)
Set.insert(ts, 6)
for k in pairs(ts) do
print(k)
end
As expected I get the numbers 1 through 6 printed out. But those calls to Set.insert(s, value) are really rather ugly. I'd much rather be able to call something like ts:insert(value).
My first attempt at a solution to this looked like this:
Set = {}
function Set.new(l)
local s = {
insert = function(t, v)
t[v] = true
end
}
for _, v in ipairs(l) do
s[v] = true
end
return s
end
ts = Set.new {1,2,3,4,5}
ts:insert(5)
ts:insert(6)
for k in pairs(ts) do
print(k)
end
This works mostly fine until you see what comes out of it:
1
2
3
4
5
6
insert
Very obviously the insert function, which is a member of the set table, is being displayed. Not only is this even uglier than the original Set.insert(s, v) problem, it's also prone to some serious trouble (like what happens if "insert" is a valid key someone is trying to enter?). It's time to hit the books again. What happens if I try this instead?:
Set = {}
function Set.new(l)
local s = {}
setmetatable(s, {__call = Set.call})
for _, v in ipairs(l) do
s[v] = true
end
return s
end
function Set.call(f)
return Set[f]
end
function Set.insert(t, v)
t[v] = true
end
ts = Set.new {1,2,3,4,5}
ts:insert(5)
ts:insert(6)
for k in pairs(ts) do
print(k)
end
Now the way I'm reading this code is:
When I call ts:insert(5), the fact that insert doesn't exist to be called means that the ts metatable is going to be searched for "__call".
The ts metatable's "__call" key returns Set.call.
Now Set.call is called with the name insert which causes it to return the Set.insert function.
Set.insert(ts, 5) is called.
What's really happening is this:
lua: xasm.lua:26: attempt to call method 'insert' (a nil value)
stack traceback:
xasm.lua:26: in main chunk
[C]: ?
And at this point I'm stumped. I have absolutely no idea where to go from here. I hacked around for an hour with varying degrees of increasingly desperate variations on this code but the end result is that I have nothing that works. What undoubtedly obvious thing am I overlooking at this point?
Now the way I'm reading this code is:
When I call ts:insert(5), the fact that insert doesn't exist to be called means that the ts metatable is going to be searched for "__call".
There's your problem. The __call metamethod is consulted when the table itself is called (ie, as a function):
local ts = {}
local mt = {}
function mt.__call(...)
print("Table called!", ...)
end
setmetatable(ts, mt)
ts() --> prints "Table called!"
ts(5) --> prints "Table called!" and 5
ts"String construct-call" --> prints "Table called!" and "String construct-call"
Object-oriented colon-calls in Lua such as this:
ts:insert(5)
are merely syntactic sugar for
ts.insert(ts,5)
which is itself syntactic sugar for
ts["insert"](ts,5)
As such, the action that is being taken on ts is not a call, but an index (the result of ts["insert"] being what is called), which is governed by the __index metamethod.
The __index metamethod can be a table for the simple case where you want indexing to "fall back" to another table (note that it is the value of the __index key in the metatable that gets indexed and not the metatable itself):
local fallback = {example = 5}
local mt = {__index = fallback}
local ts = setmetatable({}, mt)
print(ts.example) --> prints 5
The __index metamethod as a function works similarly to the signature you expected with Set.call, except that it passes the table being indexed before the key:
local ff = {}
local mt = {}
function ff.example(...)
print("Example called!",...)
end
function mt.__index(s,k)
print("Indexing table named:", s.name)
return ff[k]
end
local ts = {name = "Bob"}
setmetatable(ts, mt)
ts.example(5) --> prints "Indexing table named:" and "Bob",
--> then on the next line "Example called!" and 5
For more information on metatables, consult the manual.
You said:
Now the way I'm reading this code is:
When I call ts:insert(5), the fact that insert doesn't
exist to be called means that the ts metatable is going
to be searched for "__call".
The ts metatable's "__call" key returns Set.call.
Now Set.call is called with the name insert which causes
it to return the Set.insert function.
Set.insert(ts, 5) is called.
No, what happens is this:
When insert isn't found directly in the ts object, Lua looks for __index in its metatable.
If it is there and it is a table, Lua will search for insert there.
If it is there and it is a function, it will call it with the original table (ts in this case) and the key being searched for (insert).
If it isn't there, which is the case, it is considered nil.
The error you're having is because you don't have __index set in your metatable, so you are effectively calling a nil value.
This can be solved by pointing __index to some table, namely Set, if you're going to store your methods there.
As for __call, it is used for when you call the object as a function. Ie:
Set = {}
function Set.new(l)
local s = {}
setmetatable(s, {__index=Set, __call=Set.call})
for _, v in ipairs(l) do
s[v] = true
end
return s
end
function Set.call(s, f)
-- Calls a function for every element in the set
for k in pairs(s) do
f(k)
end
end
function Set.insert(t, v)
t[v] = true
end
ts = Set.new {1,2,3,4,5}
ts:insert(5)
ts:insert(6)
ts(print) -- Calls getmetatable(ts).__call(ts, print),
-- which means Set.call(ts, print)
-- The way __call and __index are set,
-- this is equivalent to the line above
ts:call(print)
Set = {}
function Set.new(l)
local s = {}
setmetatable(s, {__index=Set})
for _, v in ipairs(l) do
s[v] = true
end
return s
end
function Set.call(f)
return Set[f]
end
function Set.insert(t, v)
t[v] = true
end
ts = Set.new {1,2,3,4,5}
ts:insert(5)
ts:insert(6)
for k in pairs(ts) do
print(k)
end
I modified your first version and this version would offer the features I think you are looking for.
Set = {}
Set.__index = Set
function Set:new(collection)
local o = {}
for _, v in ipairs(collection) do
o[v] = true
end
setmetatable(o, self)
return o
end
function Set:insert(v)
self[v] = true
end
set = Set:new({1,2,3,4,5})
print(set[1]) --> true
print(set[10]) --> nil
set:insert(10)
print(set[10]) --> true