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

时间:2011-05-18 16:42:36

标签: lua call

我正在尝试在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表的成员。这不仅比原来的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不存在被调用的事实意味着ts metatable将被搜索"__call"
  • ts metatable的"__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]: ?

此时我很难过。我完全不知道从哪里开始。我在这段代码上乱砍了一个小时不断变化,但最终的结果是我什么都没有用。在这一点上,我忽略了什么显而易见的事情?

4 个答案:

答案 0 :(得分:18)

  

现在,我正在阅读此代码的方式是:

     
      
  • 当我调用ts:insert(5)时,不存在要调用的插入意味着将要搜索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"

Lua中面向对象的冒号调用,例如:

ts:insert(5)

仅仅是

的语法糖
ts.insert(ts,5)

本身是

的语法糖
ts["insert"](ts,5)

因此,ts上的操作不是调用,而是索引结果被称为ts["insert"]的{​​{1}},由__index元方法支配。

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

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

有关元表的更多信息,请参阅the manual

答案 1 :(得分:10)

你说:

  

现在,我正在阅读此代码的方式是:

     
      
  • 当我调用ts:insert(5)时,插入的事实并非如此   存在被称为意味着ts metatable正在进行   要搜索“__call”。
  •   
  • ts metatable的“__call”键返回Set.call。
  •   
  • 现在使用名称insert调用Set.call导致   它返回Set.insert函数。
  •   
  • 调用Set.insert(ts,5)。
  •   

不,会发生什么事:

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

您遇到的错误是因为您没有在metatable中设置__index,因此您实际上是在调用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)

答案 2 :(得分:4)

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

答案 3 :(得分:3)

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

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