Как метаметод позвонить в Lua 5.1 на самом деле работают?

Я пытаюсь, как упражнение, сделать реализацию набора в 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

очень очевидно, что функция insert, которая является членом таблицы 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

теперь я читаю этот код так:

  • когда я называю 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 ответов


теперь я читаю этот код так:

  • когда я вызываю ts: insert(5), тот факт, что insert не существует для вызова, означает, что TS metatable будет искать "__call".

вот в чем твоя проблема. The __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 - это не вызов, а индекс (the результат of ts["insert"] будучи тем, что называется), который управляется __index метаметод.

на __index метаметод может быть таблица для простого случая, где вы хотите индексирования "осень назад " к другой таблице (обратите внимание, что это значение ключа _ _ index в metatable, который индексируется и не сам метатаблиц):

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

на __index метаметодика как функция работает аналогично подписи вы ожидали с набором.вызов, за исключением того, что он передает индексируемую таблицу перед ключом:

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

для получения дополнительной информации о metatables, обратитесь руководство.


Вы сказали:

теперь я читаю этот код так:

  • когда я вызываю ts: insert (5), тот факт, что insert не exist to be called означает, что TS metatable собирается для поиска "_ _ call".
  • ключ "__call " TS metatable возвращает набор.вызов.
  • Сейчас.вызов вызывается с именем insert, которое вызывает это вернуть набор.вставить функцию.
  • Set.вставить (ts, 5) призванный.

нет, что происходит:

  • , когда insert не найден непосредственно в ts объект, Lua ищет __index в его metatable.
    • если он есть, и это таблица, Lua будет искать 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)

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 = {}
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