Как на самом деле работает метаметод __call в Lua 5.1? - PullRequest
6 голосов
/ 18 мая 2011

В качестве упражнения я пытаюсь сделать реализацию множества в 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 не существует для вызова означает, что для метатаблицы ts будет выполняться поиск "__call".
  • Ключ ts метатаблицы *1029* возвращает 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 ]

18 голосов
/ 19 мая 2011

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

  • Когда я вызываю 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"

Объектно-ориентированные вызовы двоеточия в Lua, такие как это:

ts:insert(5)

- просто синтаксический сахар для

ts.insert(ts,5)

, который сам по себе является синтаксическим сахаром для

ts["insert"](ts,5)

Таким образом, действие, которое предпринимается для ts, являетсяне вызов , а index ( результат из 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

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

10 голосов
/ 18 мая 2011

Вы сказали:

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

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

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

  • Когда insert не найден непосредственно в объекте ts, Lua ищет __index в его метатаблице.
    • Если он там и это стол, Lua будет искать там insert.
    • Если она есть и является функцией, она будет вызывать ее с исходной таблицей (в данном случае ts) и ключом, по которому выполняется поиск (insert).
    • Если его там нет, то это считается nil.

Ошибка, которую вы имеете в том, что в вашем метатаблице не установлено __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)
4 голосов
/ 18 мая 2011
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 голосов
/ 18 мая 2011

Я изменил вашу первую версию, и эта версия будет предлагать функции, которые, я думаю, вы ищете.

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
...