Метаметод поиска через __index? - PullRequest
4 голосов
/ 18 июля 2010

Я реализовал свою собственную систему классов и у меня проблемы с __tostring; Я подозреваю, что похожая проблема может возникнуть с другими метаметодами, но я не пробовал.

(Краткий обход: у каждого класса есть атрибут __classDict, содержащий все методы. Он используется в качестве экземпляров класса '__index. В то же время, __index __classDict является суперклассом' __classDict, поэтому методы в суперклассах автоматически ищутся.)

Я хотел иметь поведение "tostring по умолчанию" во всех случаях. Но это не сработало: поведение «tostring» не «правильно» распространяется по подклассам.

Я прошел этот тест, иллюстрирующий мою проблему:

mt1 = {__tostring=function(x) return x.name or "no name" end }
mt2 = {}
setmetatable(mt2, {__index=mt1})
x = {name='x'}
y = {name='y'}
setmetatable(x, mt1)
setmetatable(y, mt2)
print(x) -- prints "x"
print(mt2.__tostring(y)) -- prints "y"
print(y) -- prints "table: 0x9e84c18" !!

Я бы предпочел, чтобы в последней строке было напечатано "y".

Поведение Луа "to_String" должно использовать эквивалент

rawget(instance.class.__classDict, '__tostring')

вместо того, чтобы делать эквивалент

instance.class.__classDict.__tostring

Я подозреваю, что то же самое происходит со всеми метаметодами; rawget - используются эквивалентные операции.

Полагаю, одну вещь, которую я мог бы сделать, это скопировать все метаметоды, когда я делаю подклассы (эквивалент в приведенном выше примере будет делать mt2.__tostring = mt1.__tostring), но это отчасти не элегантно.

Кто-нибудь боролся с такой проблемой? Что, где ваши решения?

Ответы [ 4 ]

3 голосов
/ 21 июля 2010

Я подозреваю, что то же самое происходит со всеми метаметодами;используются операции, эквивалентные rawget.

Это правильно.из руководства lua:

... следует читать как rawget(getmetatable(obj) or {}, event).То есть доступ к метаметоду не вызывает другие метаметоды, а доступ к объектам без метатаблицы не прерывается (это просто приводит к нулю)

Как правило, у каждого класса есть своя метатабельность, и в него копируются все ссылки на функции.То есть сделать mt2.__tostring = mt1.__tosting

1 голос
/ 26 июля 2010

Благодаря комментариям даурниматора, я думаю, что нашел способ заставить метаметоды «следовать» __index так, как я хочу. Это сжато в этой функции:

local metamethods = {
  '__add', '__sub', '__mul', '__div', '__mod', '__pow', '__unm', '__concat', 
  '__len', '__eq', '__lt', '__le', '__call', '__gc', '__tostring', '__newindex'
}

function setindirectmetatable(t, mt) 
  for _,m in ipairs(metamethods) do
    rawset(mt, m, rawget(mt,m) or function(...)
      local supermt = getmetatable(mt) or {}
      local index = supermt.__index
      if(type(index)=='function') then return index(t,m)(...) end
      if(type(index)=='table') then return index[m](...) end
      return nil
    end)
  end

  return setmetatable(t, mt)
end

Надеюсь, это достаточно просто. Когда устанавливается новый метатабль, он инициализирует его всеми метаметодами (без замены существующих). Эти метаметоды подготовлены для «передачи» запросов «родительским метатаблицам».

Это самое простое решение, которое я мог найти. Ну, на самом деле я нашел решение, которое использовало меньше символов и было немного быстрее, но оно включало черную магию (оно включало метатабельные функции, разыменовывающие себя внутри своих собственных тел), и оно было гораздо менее читабельным, чем это.

Если кто-нибудь найдет более короткую и простую функцию, которая делает то же самое, я с удовольствием предоставлю ему ответ.

Использование простое: замените setmetatable на setindirectmetatable, когда хотите, чтобы оно "пошло вверх":

mt1 = {__tostring=function(x) return x.name or "no name" end }
mt2 = {}
setmetatable(mt2, {__index=mt1})
x = {name='x'}
y = {name='y'}
setmetatable(x, mt1)
setindirectmetatable(y, mt2) -- only change in code
print(x) -- prints "x"
print(mt2.__tostring(y)) -- prints "y"
print(y) -- prints "y"

Небольшое предупреждение: setindirectmetatable создает метаметоды на mt2. Изменение этого поведения, чтобы сделать копию, и mt2 остается неизменным, должно быть тривиальным. Но для их целей лучше всего настроить их по умолчанию.

0 голосов
/ 14 февраля 2013

Из моего опыта работы с Lua 5.1 метаметоды ищутся в метатаблицах с помощью rawget (), и поэтому вы должны скопировать ссылку на функцию в каждую создаваемую вами таблицу классов.

0 голосов
/ 19 июля 2010

См. Учебник по наследованию на вики Lua Users.

...