作为练习,我正在尝试在 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
正如预期的那样,我得到打印出数字 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
在您看到它的结果之前,它基本上工作正常:
1
2
3
4
5
6
insert
非常明显,正在显示插入函数,它是设置表的成员。这不仅比原来的 Set.insert(s, v) 问题更难看,而且还容易出现一些严重的问题(例如,如果“insert”是某人试图输入的有效密钥,会发生什么情况) ?)。又到了该看书的时候了。如果我尝试这样做会发生什么?:
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
现在我阅读此代码的方式是:
- 当我调用
ts:insert(5)
时,事实上 insert
不会t存在被调用意味着将在ts
元表中搜索<code>“__call”。
-
ts
元表的 "__call"
键返回 Set.call
。
- 现在使用名称
insert
调用 Set.call
,这会导致它返回 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]: ?
此时我被难住了。我完全不知道从这里该去哪里。我花了一个小时对这段代码进行了不同程度的、越来越绝望的变体,但最终结果是我没有任何工作。此时我忽略了哪些无疑显而易见的事情?
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?
发布评论
评论(4)
这就是你的问题。当表本身被调用(即,作为一个函数)时,
__call
元方法被查询:Lua中面向对象的冒号调用,如下所示:
仅仅是语法糖
它本身就是语法糖
,因此,对
ts
采取的操作不是调用,而是一个索引(ts["insert"] 的结果 被称为),它由__index
元方法控制。__index
元方法可以是一个表,用于您希望索引“回退”到另一个表的简单情况(请注意,它是 __index 键的值)被索引的元表,而不是元表本身):作为函数的
__index
元方法的工作方式与您期望的 Set.call 签名类似,只是它传递的表是在键之前建立索引:有关元表的更多信息,请参阅手册。
There's your problem. The
__call
metamethod is consulted when the table itself is called (ie, as a function):Object-oriented colon-calls in Lua such as this:
are merely syntactic sugar for
which is itself syntactic sugar for
As such, the action that is being taken on
ts
is not a call, but an index (the result ofts["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):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:For more information on metatables, consult the manual.
你说:
不,发生的情况是这样的:
ts
对象中没有直接找到insert
时,Lua 会在其元表中查找__index
。insert
。insert
)来调用它.nil
。您遇到的错误是因为您没有在元表中设置
__index
,因此您实际上是在调用nil
值。如果您要将方法存储在其中,可以通过将
__index
指向某个表(即Set
)来解决这个问题。至于
__call
,它用于将对象作为函数调用时。 IE:You said:
No, what happens is this:
insert
isn't found directly in thets
object, Lua looks for__index
in its metatable.insert
there.ts
in this case) and the key being searched for (insert
).nil
.The error you're having is because you don't have
__index
set in your metatable, so you are effectively calling anil
value.This can be solved by pointing
__index
to some table, namelySet
, 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:我修改了您的第一个版本,该版本将提供我认为您正在寻找的功能。
I modified your first version and this version would offer the features I think you are looking for.