作为练习,我正在尝试在Lua中进行一组实现.具体来说,我想采用Pil2 11.5的简单集合实现,并将其扩展到包括插入值,删除值等的能力.
现在,显而易见的方法(以及工作方式)是这样的:
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
Run Code Online (Sandbox Code Playgroud)
正如预期的那样,我打印出数字1到6.但那些电话Set.insert(s, value)
真的很难看.我宁愿能够打电话ts:insert(value)
.
我对此解决方案的第一次尝试看起来像这样:
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
Run Code Online (Sandbox Code Playgroud)
这很有效,直到你看到它的结果:
1
2
3
4
5
6
insert
Run Code Online (Sandbox Code Playgroud)
很明显,正在显示插入函数,它是set表的成员.这不仅比原始Set.insert(s, v)
问题更丑陋,而且还容易出现一些严重的问题(如果"插入"是某人试图进入的有效密钥会发生什么?).是时候再次上书了.如果我尝试这样做会发生什么?:
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
Run Code Online (Sandbox Code Playgroud)
现在,我正在阅读此代码的方式是:
ts:insert(5)
,insert
不存在被调用的事实意味着ts
将要搜索metatable "__call"
.ts
元表的"__call"
键返回Set.call
.Set.call
调用名称insert
,使其返回Set.insert
函数.Set.insert(ts, 5)
叫做.真正发生的是这个:
lua: xasm.lua:26: attempt to call method 'insert' (a nil value)
stack traceback:
xasm.lua:26: in main chunk
[C]: ?
Run Code Online (Sandbox Code Playgroud)
在这一点上,我很难过.我完全不知道从哪里开始.我在这段代码上乱砍了一个小时不断变化,但最终的结果是我什么都没有用.在这一点上我忽略了什么显而易见的事情?
Stu*_*ley 18
现在,我正在阅读此代码的方式是:
- 当我调用ts:insert(5)时,不存在insert的事实意味着将要搜索ts metatable的"__call".
这是你的问题.该__call
时元方法的咨询表本身被称为(即,作为一个函数):
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"
Run Code Online (Sandbox Code Playgroud)
Lua中面向对象的冒号调用如下:
ts:insert(5)
Run Code Online (Sandbox Code Playgroud)
仅仅是语法糖
ts.insert(ts,5)
Run Code Online (Sandbox Code Playgroud)
这本身就是语法糖
ts["insert"](ts,5)
Run Code Online (Sandbox Code Playgroud)
如此,对于正在采取的行动ts
不是调用,而是一个指数(该结果的ts["insert"]
是所谓),这是由支配__index
元方法.
该__index
元方法可以为你想要索引到"回落"到另一个表的简单情况表(注意,这是在__index键的值是被收录在元表并没有元表本身):
local fallback = {example = 5}
local mt = {__index = fallback}
local ts = setmetatable({}, mt)
print(ts.example) --> prints 5
Run Code Online (Sandbox Code Playgroud)
__index
作为函数的metamethod与Set.call所期望的签名类似,只是它传递了在键之前被索引的表:
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
Run Code Online (Sandbox Code Playgroud)
有关元表的更多信息,请参阅手册.
Zec*_*ecc 10
你说:
现在,我正在阅读此代码的方式是:
- 当我调用ts:insert(5)时,不存在insert的事实意味着将要搜索ts metatable的"__call".
- ts metatable的"__call"键返回Set.call.
- 现在使用名称insert调用Set.call,使其返回Set.insert函数.
- 调用Set.insert(ts,5).
不,这是怎么回事:
insert
没有直接在ts
对象中找到,Lua会__index
在其metatable中查找.insert
那里搜索.ts
在这种情况下)和正在搜索的键(insert
)调用它.nil
. 你遇到的错误是因为你没有__index
在metatable中设置,所以你实际上是在调用一个nil
值.
这可以通过指向__index
某个表来解决,即Set
,如果您要将方法存储在那里.
至于__call
,它用于将对象作为函数调用时.即:
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)
Run Code Online (Sandbox Code Playgroud)