This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
Sewbacca's profile picture

Improve class prototype

Started by Sewbacca, 18 October 2018 - 12:08 PM
Sewbacca #1
Posted 18 October 2018 - 02:08 PM
Okay, i have the following class definition:


local class = { }
setmetatable(class, class)

--# Standard methods

--# virtual
function class:__call(...)
	--# Call the constructor for a nice and clean syntax (class())
	return self:construct(...)
end

--# virtual
function class:construct(...)
	--# Constructs an object
	local object = { }
	setmetatable(object, self)
	self.__index = self

	local ok, err = pcall(self.init, object, ...)
	if not ok then error(err, 2) end
	return object
end

--# virtual
function class:init()
	--# Standard constructor with no special construction
end

return class

Goal:

--# Constructing
local object = class()
local TestClass = class()
local InheritClass = TestClass()

--# Adding methods

function TestClass:method()
	--# Do stuff
end

--# Adding metamethods

function TestClass:__tostring()
	return "string"
end

The first things are working quite well, also with InheritClass, but the metamethods won't be inherited, because somehow, lua is ignoring the metatable of the metatable.
I could create methods for each metamethods and redirecting to the class itself, but then how would the index method or table work properly?
And with the current design, textutils screws up everything: It can't index a table with an index to itself and having itself as a metatable.

Any help appreciated and thank you for any answer.

Sewbacca
Edited on 18 October 2018 - 12:09 PM
Lyqyd #2
Posted 20 October 2018 - 03:48 AM

local function magic(t, k)
    local target = t
    while true do
        local value = rawget(target, k)
        if value ~= nil then return value end
        target = rawget(target, "super")
        if target == nil then return nil end
    end
end

local function add(obj, name, parent)
    setmetatable(obj, {__index = magic)
    obj.super = parent
end

Not heavily tested, but gives your objects a "super" field to trace their lineage upward, and an __index metamethod to step through that sequence.
Lupus590 #3
Posted 20 October 2018 - 08:01 AM

local function magic(t, k)
	local target = t
	while true do
		local value = rawget(target, k)
		if value ~= nil then return value end
		target = rawget(target, "super")
		if target == nil then return nil end
	end
end
local function add(obj, name, parent)
	setmetatable(obj, {__index = magic}) --# add missing }
	obj.super = parent
end
Edited on 20 October 2018 - 06:01 AM
Sewbacca #4
Posted 23 October 2018 - 11:27 AM

local function magic(t, k)
	local target = t
	while true do
		local value = rawget(target, k)
		if value ~= nil then return value end
		target = rawget(target, "super")
		if target == nil then return nil end
	end
end

local function add(obj, name, parent)
	setmetatable(obj, {__index = magic)
	obj.super = parent
end

Not heavily tested, but gives your objects a "super" field to trace their lineage upward, and an __index metamethod to step through that sequence.

That would allow me, to use a single metable for all my classes right? I think that would work, but I'm not too confident with such a solution cause of the speadloss during a indexing. I would prefer a __index table instead:


function new(parent)
  local obj = setmetatable({}, parent)
  parent.__index = parent
  return obj
end

local obj = new(parentclass)
[Assuming, that parentclass contains all methods, etc.]

This would allow me the following code:

function parentclass:__add(a, B)/>/>/>
  return a.val, b.val
end

A metamethod, setted up in the class itself, but metamethods won't be inherited when i create a inherit class:

local inheritClass = new(parentclass)
local obj2 = new(inheritClass)
--# obj2 won't have a metamethod __add

That's my problem. If i use a overall metatable with an __index method setted to magic, then I could solve this problem,
but I would like to use a table for __index instead, cause of the speed loss, but I will keep your solution in mind, if i can't find another.
Thanks!
Edited on 23 October 2018 - 09:29 AM
EveryOS #5
Posted 23 October 2018 - 01:02 PM
Copy the metamethods?


local class = {}
class.new = function()
  local rtn = {}
  setmetatable(rtn, {__index = class})

  return rtn
end
local class2 = {}
setmetatable(class2, {__index = class})
class2.new = function()
  local rtn = {}
  local mt = {}
  for k, v in pairs(getmetatable(class)) do mt[k] = v end
  mt.__index = class2
  setmetatable(rtn,mt)

  return rtn
end

Just a guess, I don't use metatables much and I might be reading your question wrong…
Edited on 23 October 2018 - 04:05 PM
Sewbacca #6
Posted 23 October 2018 - 04:04 PM
Copy the metamethods?


local class = {}
class.new = function()
  local rtn = {}
  setmetatable(rtn, {__index = class})

  return rtn
end
local class2 = {}
setmetatable(class2, {__index = class})
class2.new = function()
  local rtn = {}]
  local mt = {}
  for k, v in pairs(getmetatable(class)) do mt[k] = v end
  mt.__index = class2
  setmetatable(rtn,mt)

  return rtn
end

Just a guess, I don't use metatables much and I might be reading your question wrong…

That would probably work with one downside. If you inherit a class and down change the super class after creating the inheritance class, then it would work properly, but if not, the new method wouldn't be inherited:


superclass = class()
inheritanceClass = class(superclass)
function superclass:method()
end
local obj = inheritcanceClass:new()
function superclass:method(var)
  print("Value: ", var)
end
obj:method("hello")
-- No output, because he has the method
EveryOS #7
Posted 23 October 2018 - 06:14 PM
After making some syntax error fixes and mods I get this


local class = {}
class.new = function(self)
  local rtn = {}
  setmetatable(rtn, {__index = class})
  return rtn
end
class.method = function() end
local class2 = {}
setmetatable(class2, {__index = class})
class2.new = function(self)
  local rtn = {}
  local mt = {}
  for k, v in pairs(class) do mt[k] = v end
  mt.__index = class2
  setmetatable(rtn, mt)
  return rtn
end
local superclass = class:new()
local inheritanceClass = class2:new()
local obj = inheritanceClass:new()
class.method = function(self, var)
  print("Value: ", var)
end
obj:method("hello")

On Mimic I get "Value: hello"

I will look into other metatable values
EveryOS #8
Posted 23 October 2018 - 06:46 PM
COMMENT DELETED
~EveryOS
Edited on 24 October 2018 - 11:42 AM
Sewbacca #9
Posted 23 October 2018 - 08:49 PM
After making some syntax error fixes and mods I get this


local class = {}
class.new = function(self)
  local rtn = {}
  setmetatable(rtn, {__index = class})
  return rtn
end
class.method = function() end
local class2 = {}
setmetatable(class2, {__index = class})
class2.new = function(self)
  local rtn = {}
  local mt = {}
  for k, v in pairs(class) do mt[k] = v end
  mt.__index = class2
  setmetatable(rtn, mt)
  return rtn
end
local superclass = class:new()
local inheritanceClass = class2:new()
local obj = inheritanceClass:new()
class.method = function(self, var)
  print("Value: ", var)
end
obj:method("hello")

On Mimic I get "Value: hello"

I will look into other metatable values

I said something wrong, i meant, if you copy metatable functions, you will lose inheritance, but for now, i couldn't figure out a inheritance of metamethods.

Here is something:

--A table called class that does class-type stuff
local class = {}
class.__index = class
class.extend = function(self, cclass)
  class2.__index = self
  setmetatable(cclass, cclass)

  return class2
end
setmetatable(class, class)

Here is a script that uses something like this:

--A table called class that does class-type stuff
local class = {}
class.__index = class
class.extend = function(self, cclass)
  cclass.__index = self
  setmetatable(cclass, cclass)
  return cclass
end
setmetatable(class, class)
local function new(val, ...)
local r = ((val.parent or class):extend(val))
r(...)
return r
end
--After class definition
local number = {
__call = function(self, number)
  self.number = number
end,
__add = function(self, val)
  if type(val) == "number" then
   return self.number + val
  else
   return self.number + val.number
  end
end
}
local wholeNumber = {
parent = number,
__call = function(self, val)
   if math.floor(val) ~= val then error("Not whole") end
   self.parent.__call(self, val)
	end,
	__add = number.__add
}
local n = new(number, 4.2)
print("TYPE OF N: "..type(n))
print(n + 5)
print(n + new(wholeNumber, 3))
print(n + new(wholeNumber, 4.2))

Note that metamethods must be manually defined

Can you please explain me what you are trying to show?
EveryOS #10
Posted 24 October 2018 - 12:58 PM
I don't really remember
I think that in some programming languages you have to redefine metamethods/constructors, which is kinda what I did above. Other than that, all of the methods are passed down. With the above, you would have to change wholeNumber's add, but you can easily change the parent class (:

The problem is that
metatable.__method does not refer to metatable.__index.__method, though that is probably good because otherwise we would create an infinite loop