Jump to content




Improve class prototype

lua

9 replies to this topic

#1 Sewbacca

  • Members
  • 450 posts
  • LocationStar Wars

Posted 18 October 2018 - 01: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 by Sewbacca, 18 October 2018 - 01:09 PM.


#2 Lyqyd

    Lua Liquidator

  • Moderators
  • 8,459 posts

Posted 20 October 2018 - 02: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.

#3 Lupus590

  • Members
  • 2,007 posts
  • LocationUK

Posted 20 October 2018 - 07:01 AM

View PostLyqyd, on 20 October 2018 - 02:48 AM, said:

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 by Lupus590, 20 October 2018 - 07:01 AM.


#4 Sewbacca

  • Members
  • 450 posts
  • LocationStar Wars

Posted 23 October 2018 - 10:27 AM

View PostLyqyd, on 20 October 2018 - 02:48 AM, said:

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 by Sewbacca, 23 October 2018 - 10:29 AM.


#5 EveryOS

    Resident Necromancer

  • Members
  • 569 posts
  • LocationBOO!!

Posted 23 October 2018 - 12: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 by EveryOS, 23 October 2018 - 05:05 PM.


#6 Sewbacca

  • Members
  • 450 posts
  • LocationStar Wars

Posted 23 October 2018 - 03:04 PM

View PostEveryOS, on 23 October 2018 - 12:02 PM, said:

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


#7 EveryOS

    Resident Necromancer

  • Members
  • 569 posts
  • LocationBOO!!

Posted 23 October 2018 - 05: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

#8 EveryOS

    Resident Necromancer

  • Members
  • 569 posts
  • LocationBOO!!

Posted 23 October 2018 - 05:46 PM

COMMENT DELETED
~EveryOS

Edited by EveryOS, 24 October 2018 - 12:42 PM.


#9 Sewbacca

  • Members
  • 450 posts
  • LocationStar Wars

Posted 23 October 2018 - 07:49 PM

View PostEveryOS, on 23 October 2018 - 05:14 PM, said:

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.

View PostEveryOS, on 23 October 2018 - 05:46 PM, said:

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?

#10 EveryOS

    Resident Necromancer

  • Members
  • 569 posts
  • LocationBOO!!

Posted 24 October 2018 - 11:58 AM

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





1 user(s) are reading this topic

0 members, 1 guests, 0 anonymous users