Как метаметод позвонить в 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
.
- если он есть, и это таблица, Lua будет искать
ошибка у вас есть, потому что у вас нет __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