Метаметоды и классы

Я создал функцию, аналогичную показанной здесь, и у меня возникли проблемы с метаметодом __add. Я хочу иметь возможность использовать метаметод __add в двух экземплярах класса, но единственный способ, которым он работает, - добавить метаметоды в метатаблицу экземпляров. Есть ли способ, которым я мог бы настроить это на работу так, чтобы мой класс или его метатабельность могли иметь метаметод __add и работать при добавлении экземпляров вместе?

function Class(superClass, constructor)
    local class = {};
    local metatable = {};

    if superClass then
        metatable.__index = superClass;
    end

    setmetatable(class, metatable);

    function metatable:__call(...)
        local instance = {};
        setmetatable(instance, { __index = class });

        if class.constructor then
            class.constructor(instance, ...);
        end

        return instance;
    end

    class.constructor = constructor;

    return class;
end

Вот пример того, что я хотел бы сделать:

A = Class(nil, function(self, num)
    self.num = num;
end);

function A:__add(rhs)
    return A(self.num + rhs.num);
end

a = A(1);
b = A(2);

c = a + b;

2 ответа

Благодаря помощи полковника Тридцать два и Итана Рейснера я нашел следующее решение:

function metatable.__call(self, ...)
    local instance = {};
    setmetatable(instance, self);

    if class.constructor then
        class.constructor(instance, ...);
    end

    return instance;
end

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

Я попробовал несколько вещей, которые нашел в другом месте, и нашел это рабочее решение:

function Class(superClass, constructor)
    local class = {};

    if superClass then
        for k,v in pairs(superClass) 
        do
            class[k] = v;
        end

        class._superClass = superClass;
    end

    class.__index = class;

    local metatable = {};

    function metatable:__call(...)
        local instance = {};
        setmetatable(instance, class);

        if class.constructor then
            class.constructor(instance, ...);
        end

        return instance;
    end

    class.constructor = constructor;

    setmetatable(class, metatable);

    return class;
end

На всякий случай это помогает, вот как я обычно создаю свои классы (также легко поддерживает наследование):

function Vector(x, y)
  local self = {}
  self.x = x
  self.y = y
  self.magnitude = math.sqrt(x*x + y*y)

  function self.unit()
    return Vector(x/self.magnitude, y/self.magnitude)
  end

  function self.printComponents()
    print("[" .. self.x .. ", " .. self.y .. "]")
  end

  --if you want to implement metamethods...
  setmetatable(self, {
    __add = function(_, other)
      return Vector(self.x + other.x, self.y + other.y)
    end
  })

  return self
end

-- now, let's make a ComplexNumber class that inherits from Vector

function ComplexNumber(a, b)
  -- define self as an instance of Vector
  local self = Vector(a, b)

  -- override Vector.printComponents()
  function self.printComponents()
    print(self.x .. " + " .. self.y .. "i")
  end

  return self
end

--examples
local v = Vector(10, 20)
v.printComponents() --> [10, 20]

local c = ComplexNumber(10, 5)
c.printComponents() --> 10 + 5i
Другие вопросы по тегам