Lua 5.1 中的 __call 元方法实际上是如何工作的?

发布于 2024-11-08 13:17:46 字数 2075 浏览 0 评论 0 原文

作为练习,我正在尝试在 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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

囚你心 2024-11-15 13:17:46

现在我阅读这段代码的方式是:

  • 当我调用 ts:insert(5) 时,insert 不存在可被调用的事实意味着将在 ts 元表中搜索“__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"

Lua中面向对象的冒号调用,如下所示:

ts:insert(5)

仅仅是语法糖

ts.insert(ts,5)

它本身就是语法糖

ts["insert"](ts,5)

,因此,对 ts 采取的操作不是调用,而是一个索引ts["insert"] 的结果 被称为),它由 __index 元方法控制。

__index 元方法可以是一个表,用于您希望索引“回退”到另一个表的简单情况(请注意,它是 __index 键的)被索引的元表,而不是元表本身):

local fallback = {example = 5}
local mt = {__index = fallback}
local ts = setmetatable({}, mt)
print(ts.example) --> prints 5

作为函数的 __index 元方法的工作方式与您期望的 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

有关元表的更多信息,请参阅手册

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.

余生再见 2024-11-15 13:17:46

你说:

现在我阅读这段代码的方式是:

  • 当我调用 ts:insert(5) 时,事实上 insert 并没有
    存在被调用意味着 ts 元表正在运行
    搜索“__call”。
  • ts 元表的“__call”键返回 Set.call。
  • 现在使用名称 insert 调用 Set.call ,这会导致
    它返回 Set.insert 函数。
  • 调用 Set.insert(ts, 5)。

不,发生的情况是这样的:

  • 当在 ts 对象中没有直接找到 insert 时,Lua 会在其元表中查找 __index
    • 如果它在那里并且是一个表,Lua 将在那里搜索insert
    • 如果它存在并且是一个函数,它将使用原始表(本例中为 ts)和正在搜索的键(insert)来调用它.
    • 如果不存在(在这种情况下),则将其视为nil

您遇到的错误是因为您没有在元表中设置 __index ,因此您实际上是在调用 nil 值。

如果您要将方法存储在其中,可以通过将 __index 指向某个表(即 Set)来解决这个问题。

至于__call,它用于将对象作为函数调用时。 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)

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)
甜心小果奶 2024-11-15 13:17:46
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
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
猫弦 2024-11-15 13:17:46

我修改了您的第一个版本,该版本将提供我认为您正在寻找的功能。

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

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文